﻿//For public functions (used by manage page)
var feedback = {};

//The following variable are used when feedback is running within a local prototype
var authCookieValue = null;

//Content URL path is relative if prototype is hosted on Axshare...
var contentUrlPrefix = '';

// Months of our lives
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

//------------------------------------------------------------------------------------
// Utility
//------------------------------------------------------------------------------------

// Retrieve a cookie
function getCookieValue(cookieName, decodeUri) {
    var nameEq = cookieName + '=';
    var retVal = null;
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEq) == 0) {
            retVal = c.substring(nameEq.length, c.length);
        }
    }

    if (!!retVal && decodeUri == true) {
        return decodeURIComponent(retVal);
    }

    return retVal;
};

// Grabs the value of a fragment variable if present
function getFragmentVariable(variable) {
    var query = window.location.hash.substring(1);
    var vars = query.split("&");
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        if (pair[0] == variable) {
            return pair[1];
        }
    }
    return false;
};

// Adds or Removes a fragment variable (remove by passing 'null' into 'value')
function addRemoveFragmentVariable(name, value) {
    if (value != null) {
        if(window.location.hash === "") {
            window.location.hash += name + '=' + value;
        } else {
            window.location.hash += '&' + name + '=' + value;
        }
    } else {
        var query = window.location.hash.substring(1);
        var vars = query.split("&");
        for(var i = 0; i < vars.length; i++) {
            var pair = vars[i].split("=");
            if(pair[0] === name) {
                window.location.hash = window.location.hash.replace(name + '=' + pair[1], '');
                window.location.hash = window.location.hash.replace('#&', '#');
                window.location.hash = window.location.hash.replace('&&', '&');
                if(window.location.hash.charAt(window.location.hash.length - 1) === '&') {
                    window.location.hash = window.location.hash.substring(0, window.location.hash.length - 1);
                }
            }
        }
    }


};

// Copied from messagecenter.js, just checks if this is a chrome file url prototype
function isLocalChrome() {
    if($axure.browser !== undefined && !$axure.browser.isEdge) {
        var chromeRegex = /Chrome\/([0-9]+).([0-9]+)/g;
        var chromeMatch = chromeRegex.exec(window.navigator.userAgent);
        var CHROME = Boolean(chromeMatch);
        var CHROME_5_LOCAL = chromeMatch &&
            Number(chromeMatch[1]) >= 5 &&
            location.href.indexOf('file://') >= 0;

        return CHROME_5_LOCAL == null ? false : CHROME_5_LOCAL;
    }

    return false;
};

// Checks if the prototype is locally generated on disk or locally previewed
function isLocal() {
    return location.href.indexOf('file://') >= 0 || location.href.indexOf('127.0.0.1') >= 0;
};

// Checks if the browser is Mozilla Firefox
function isFirefox() {
    return typeof InstallTrigger !== 'undefined';
};

// Checks if the browser is Safari 3.0+
// https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
function isSafari() {
    // Safari 3.0+ "[object HTMLElementConstructor]" 
    var liveSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
    var fileUrlSafari = isLocal() && !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/);

    return liveSafari || fileUrlSafari;
};

// Does feedback plugin redirection and scrolling to respective issue/comment dependent on fragment variables
function showIssueOrComment() {
    var type = getFragmentVariable('type');
    //var show = getFragmentVariable("show");

    if (type === false /*|| show != false*/) { return; }

    var p = getFragmentVariable("p");

    var newIssue = getFragmentVariable("newIssueText");
    var newIssueText = newIssue === false ? "" : decodeURIComponent(newIssue);

    var newCommentId = getFragmentVariable("newCommentId");
    var newComment = getFragmentVariable("newCommentText");
    var newCommentText = newComment === false ? "" : decodeURIComponent(newComment);

    var issueCode = getFragmentVariable("issueCode");
    var commentId = getFragmentVariable("commentId");

    // Show feedback tab
    $("#interfaceControlFrameHeader [pluginid='feedbackHost']").click();

    // Jump back to adding a new issue (Coming from after installing extension)
    if (newIssueText !== "") {
        (function waitforIssue() {
            var $newIssue = $('#addTopicInput');
            if (window.loaded && $newIssue.length) {
                $newIssue.val(newIssueText);
                $newIssue.focus();
            } else {
                setTimeout(waitforIssue, 1000);
            }
        })();
    }

    // Scroll to Issue and Comment
    if (issueCode !== false) {
        (function issueLoad() {
            var $issue = $('#issue_' + issueCode);
            if ($issue.length) {

                var $commentBlock = $issue.find('.issueComments');

                // Issue scrollTo
                $issue.get(0).scrollIntoView();

                if (type === "comment") {
                    if (parseInt($issue.find('.replyCount').text()) > 0) {
                        $issue.find('.showRepliesButton').click();

                        // Viewing a specific comment
                        if (commentId) {
                            (function wait() {
                                var $comment = $('#comment_' + commentId);
                                if ($commentBlock.children().size() > 1 || $comment.length) {
                                    if ($comment.length) {
                                        $comment.get(0).scrollIntoView();
                                    }
                                } else {
                                    setTimeout(wait, 500);
                                }
                            })();
                        }
                    }

                    // Jump back to adding a new comment into an exisiting issue (Coming back from instaling extension)
                    if (newCommentId) {
                        (function waitForReplyButton() {
                            var $showRepliesButton = $issue.find('.showRepliesButton');
                            var $replyToggleButton = $issue.find('.replyToggleButton');

                            if ($showRepliesButton.length || $replyToggleButton.length) {
                                // Has exisiting comments
                                if (parseInt($issue.find('.replyCount').text()) > 0) {
                                    $showRepliesButton.click();

                                    // Need to expand all comments and wait for next 'Reply' button
                                    (function waitForToggleButton() {
                                        var $issueReplyToggleButton = $issue.find('.issueReplyToggleButton');
                                        if ($issueReplyToggleButton.length) {
                                            $issueReplyToggleButton.click();

                                            // Wait for the textbox to show up
                                            (function waitForTextBox() {
                                                var $feedbackEntryTextArea = $issue.find('.feedbackEntryTextArea');
                                                if ($feedbackEntryTextArea.length) {
                                                    if (newCommentText !== "") {
                                                        $feedbackEntryTextArea.val(newCommentText);
                                                    }

                                                    $feedbackEntryTextArea.get(0).scrollIntoView();
                                                } else {
                                                    setTimeout(waitForTextBox, 500);
                                                }
                                            })();
                                        } else {
                                            setTimeout(waitForToggleButton, 500);
                                        }
                                    })();
                                } else {
                                    // No exisiting comments case
                                    $replyToggleButton.click();

                                    //Wait for the textbox to show up
                                    (function waitForTextBox() {
                                        var $feedbackEntryTextArea = $issue.find('.feedbackEntryTextArea');
                                        if ($feedbackEntryTextArea.length) {
                                            if (newCommentText !== "") {
                                                $feedbackEntryTextArea.val(newCommentText);
                                            }

                                            $feedbackEntryTextArea.get(0).scrollIntoView();
                                        } else {
                                            setTimeout(waitForTextBox, 500);
                                        }
                                    })();
                                }
                            } else {
                                setTimeout(waitForReplyButton, 500);
                            }
                        })();
                    }
                }
            } else {
                setTimeout(issueLoad, 1000);
            }
        })();
    };

    // Can't change url w/out refreshing page so just trick the url
    // so when there is a refresh, the page won't jump to the discussions
    //window.location.hash += "&show=false";
    window.location.hash = "p=" + p;
}

// Checks if a given element is visible and within the given scroll container
function checkIfElemWithinScrollView($elem, $scrollContainer, $staticContainer) {
    var top = $scrollContainer.scrollTop();
    var bottom = top + ($staticContainer == null ? $scrollContainer.height(): $staticContainer.height());
    var left = $scrollContainer.scrollLeft();
    var right = left + ($staticContainer == null ? $scrollContainer.width() : $staticContainer.width());

    var position = $elem.position();

    var elementTop = position.top;
    var elementBottom = elementTop + $elem.height();
    var elementLeft = position.left;
    var elementRight = elementLeft + $elem.width();

    return (((top <= elementTop) && (bottom >= elementBottom)) && ((left <= elementLeft) && (right >= elementRight)));
};

// Prevents any other scrollable divs from scrolling outside of the designated div
// http://stackoverflow.com/questions/7600454/how-to-prevent-page-scrolling-when-scrolling-a-div-element
$.fn.isolatedScroll = function() {
    this.bind('mousewheel DOMMouseScroll', function(e) {
        var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail;
        var bottomOverflow = this.scrollTop + $(this).outerHeight() - this.scrollHeight >= 0;
        var topOverflow = this.scrollTop <= 0;

        if((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
            e.preventDefault();
        }

        return this;
    });
};


//------------------------------------------------------------------------------------
// Callback Functions
//------------------------------------------------------------------------------------

// Shows the dialog to link
function buildAndShowNoScreenshotDialog(source) {
    function resizeWhenVisible() {
        if ($('#missingExtensionDialog').is(':visible')) {
            setTimeout(function () {
                var width = parseInt($('#feedbackHost').css('width')) - 40 + 'px';      // 40 for the padding and margin of the dialog
                $('#missingExtensionDialog').css('width', width);

                var left = $('#feedbackHost').position().left + ((parseInt($('#feedbackHost').css('width')) - parseInt($('#missingExtensionDialog').css('width')) - 20) / 2);
                $('#missingExtensionDialog').css('left', left + 'px');
            }, 100);
        } else {
            setTimeout(resizeWhenVisible(), 50);
        }
    }
    function repositionMissingExtensionDialog() {
        $('#missingExtensionDialogOverlay').css('left', $('#feedbackHost').position().left);
        $('#missingExtensionDialogOverlay').css('height', $('#feedbackHost').css('height'));
        $('#missingExtensionDialogOverlay').css('width', $('#feedbackHost').css('width'));

        resizeWhenVisible();
    };

    // Missing Extension Dialog
    if (!$('#missingExtensionDialog').length && $axscreenshot.isScreenshotCompatible) {
        var dialog = document.createElement('div');
        dialog.setAttribute('id', 'missingExtensionDialog');
        dialog.setAttribute('class', 'dialogDiv');

        var content = [
            'Please install our browser extension to capture screenshots.<br/><br/>',
            '<div id="linkButton" class="feedbackGreenBtn" title="Download Extension" style="cursor: pointer" onclick="window.open(', ($axscreenshot.isChrome ? $axscreenshot.chromeLink : $axscreenshot.ffLink), ')">DOWNLOAD EXTENSION</div>',
            '<br/>After installing, <a id="reloadLink" href="#">reload this page</a>.'
        ].join("");

        $('#feedbackHost').append(dialog);
        $(dialog).append(content);

        $('#missingExtensionDialog').axDialog();
        $('#missingExtensionDialog').axDialog('show');
        repositionMissingExtensionDialog();

        $('#reloadLink').click(function () {
            // Get Issue text
            var newIssueText = $('.issueFeedbackEntryTextArea').val();

            // Get Comment's Issue code
            var newCommentIssueCode = $(source).parents('.feedbackEntryItem').attr('issuecode');

            // Get Comment Id
            var newCommentId = $(source).parents('.feedbackEntryItem').attr('id');

            // Get Comment Text
            var newCommentText = $(source).parents('.feedbackEntryItem').find('.commentFeedbackEntryTextArea').val();

            // Checks if screenshot icon was clicked from a comment
            var isNewComment = !$(source).hasClass('screenshotIssueIcon');

            window.location.hash
                += (newIssueText !== "" ? "&newIssueText=" + encodeURIComponent(newIssueText) : "")
                + (isNewComment && newCommentIssueCode.length && newCommentId.length
                    ? "&type=comment&issueCode=" + newCommentIssueCode + "&newCommentId=" + newCommentId
                    : "&type=issue")
                + (newCommentText !== "" && newCommentText != null ? "&newCommentText=" + encodeURIComponent(newCommentText) : "");

            location.reload(true);
            //location.reload();
        });

        $(window).resize(function () {
            repositionMissingExtensionDialog();
        });
    }

    if ($axscreenshot.isScreenshotCompatible) {
        $('#missingExtensionDialog').axDialog('show');
        repositionMissingExtensionDialog();
    }
};

// Event function. To be performed when the camera icon is clicked
function executeScreenshot(elem) {
    // Operations to perform prior to taking a screenshot
    function preScreenshot() {
        if(window.$axpin != null) {
            $axpin.showPins(false, true);
        }
    };
    // Operations to perform after cropping is done or the screenshot is canceled
    function postScreenshot() {
        if(window.$axpin != null) {
            $axpin.showPins(true, true, true);
        }
    };

    // Call plugin to perform screenshot and cropping process
    $axscreenshot.takeScreenshot(elem, buildAndShowNoScreenshotDialog, preScreenshot, postScreenshot);
};


//------------------------------------------------------------------------------------
// Main Executing Logic
//
// Encapsulated function is to isolate the scope of this plugin
//------------------------------------------------------------------------------------
; (function () {
    // Axure Account
    var userId = '';
    var userName = '';
    var userEmail = '';
    var userProfileImg = '';
    var isUsingAxureAcct = false;
    var isSubInstance = false;

    // Watermark hints
    // NOTE: The trailing characters serve to be a distinguishing element in case the user actually does use text similar to the hint.
    var reminderString           = "Please add a message or screenshot before you post";
    var commentReminderString    = "Enter your comment here           ";
    var replyReminderString      = "Enter your reply here             ";
    var emailHint                = "Email               ";
    var passHint                 = "Password             ";
    var textAreaCharLimit        = "Maximum 1000 Characters";
    var textAreaLengthError      = "Post Failed - There may be too many characters";

    // Anon commenter name
    var localCommenterName = null;

    // Is Manage Page
    var isManagePage = false;

    // For read only mode (when performing maintenance)
    var readOnlyMode = false;
    var readOnlyMessage = '';

    // Issue Polling
    var isPollingEnabled = true;
    var lastPollTime;

    // The following two variables are used when feedback is running within a local prototype
    var authCookieValue = null;
    var userCookieValue = null;

    if (window.ACCOUNT_SERVICE_SECURE_URL == null) {
        window.ACCOUNT_SERVICE_SECURE_URL = 'https://accounts.axure.com';
    }
    var feedbackServiceUrl = (window.AXSHARE_HOST_SECURE_URL || 'https://share.axure.com') + '/issue';
    var accountServiceUrl = (window.ACCOUNT_SERVICE_SECURE_URL || 'https://accounts.axure.com');

    // Else if a local prototype, make content path absolute.
    if ($axure.player.settings.isAxshare === false) {
        contentUrlPrefix = window.AXSHARE_HOST_URL || 'http://share.axure.com';
    }


    //---------------------------------------------------------------------------------------------------------------------------
    //----------INTERNAL UTILITY FUNCTIONS---------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------

    // Determines if browser is mobile
    function isMobile() {
        var userAgent = navigator.userAgent || navigator.vendor || window.opera;

        return (/window phone/i.test(userAgent)) ||
            (/android/i.test(userAgent)) ||
            (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream);
    };

    // Returns true of the given element is an array.
    function isArray(testObject) {
        return testObject && !(testObject.propertyIsEnumerable('length')) && typeof testObject === 'object' && typeof testObject.length === 'number';
    };

    // Gets an approximate relative time from when we display the issues/comments and when they were initially created.
    function buildRelativeDate(current, previous) {
        var msPerMinute = 60 * 1000;
        var msPerHour = msPerMinute * 60;
        var msPerDay = msPerHour * 24;
        var msPerMonth = msPerDay * 30;
        var msPerYear = msPerDay * 365;

        // values should be in milliseconds
        var elapsed = current - previous;
        var value = 0;

        if (elapsed < msPerMinute) {
            value = Math.round(elapsed / 1000);

            return value === 1 ? "1 second ago" : value + " seconds ago";
        } else if (elapsed < msPerHour) {
            value = Math.round(elapsed / msPerMinute);

            return value === 1 ? "1 minute ago" : value + ' minutes ago';
        } else if (elapsed < msPerDay) {
            value = Math.round(elapsed / msPerHour);

            return value === 1 ? "1 hour ago" : value + ' hours ago';
        } else if (elapsed < msPerMonth) {
            value = Math.round(elapsed / msPerDay);

            return (value === 1 ? "Yesterday" : (value === 7 ? "about 1 week ago" : "about " + value + " days ago"));
        } else if (elapsed < msPerYear) {
            value = Math.round(elapsed / msPerMonth);

            return "about " + (value === 1 ? "1 month ago" : value + " months ago");
        } else {
            value = Math.round(elapsed / msPerYear);

            return "about " + (value === 1 ? "1 year ago" : value + " years ago");
        }
    };

    // Get a date formatted as MM dd, YYYY
    function buildResolvedDate(aspDate) {
        var date = new Date(Date.parse(aspDate));

        return months[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear();
    };

    // Fixes issue date fields and returns the issues sorted by modified date or created date if it hasn't yet been modified.
    function fixIssueDatesAndSort(issues, sortFn) {
        var items = issues;
        if (!isArray(issues)) {
            items = [issues];
        }

        $.each(items, function (index, item) {
            var dateStr = item.createdDate;
            item.createdDate = new Date(Number(dateStr.substring(6, dateStr.length - 2)));

            dateStr = item.modifiedDate;
            if (dateStr != null) {
                item.modifiedDate = new Date(Number(dateStr.substring(6, dateStr.length - 2)));
            }
        });

        if (!sortFn) {
            sortFn = function (x, y) {
                var a = x.modifiedDate ? x.modifiedDate : x.createdDate;
                var b = y.modifiedDate ? y.modifiedDate : y.createdDate;

                if (a === b) return 0;
                else if (a < b) return 1;
                else return -1;
            };
        }
        items.sort(sortFn);
        return items;
    };

    // Fixes comment date fields and returns the comments sorted by created date
    function fixCommentDatesAndSort(comments, sortFn) {
        var items = comments;
        if (!isArray(comments)) {
            items = [comments];
        }

        $.each(items, function (index, item) {
            var dateStr = item.commentDate;
            item.commentDate = new Date(Number(dateStr.substring(6, dateStr.length - 2)));

            dateStr = item.commentModifiedDate;
            if (dateStr != null) {
                item.commentModifiedDate = new Date(Number(dateStr.substring(6, dateStr.length - 2)));
            }
        });

        if (!sortFn) {
            sortFn = function (x, y) {
                var a = x.commentDate;
                var b = y.commentDate;

                if (a === b) return 0;
                else if (a < b) return -1;
                else return 1;
            };
        }
        items.sort(sortFn);
        return items;
    };

    // Given a jquery selection array, find the element with a given value attribute that is the largest
    function findObjectWithLargestAttrValue($arr, attr) {
        var $elem;
        var val = 0;        // TODO: Find a programatic min

        $arr.each(function(index, element) {
            var currVal = parseInt($(element).attr(attr));
            if(currVal > val) {
                $elem = $(element);
                val = currVal;
            }
        });

        return $elem;
    }

    // Just clears and prepares an empty pin panel.
    function reloadPins() {
        if(isLocalChrome()) return;

        if(window.$axpin != null) {
            $axpin.showPins(false, true);
        }

        $($('#rightPanel').find('iframe')).one("load", function () {
            if (window.$axpin != null) {
                $axpin.prepareExistingPins();
            }
        });
    }

    // Back and Next Button functionality. Trying to ensure functions exist.
    function fireWhenReady() {
        if (openNextPage != null && openPreviousPage != null && $('#feedbackButtonBack').length !== 0 && $('#feedbackButtonForward').length !== 0 && $('.sitemapPageLink').length !== 0) {
            $('#feedbackButtonBack').click(function () {
                reloadPins();
                openPreviousPage();
            });
            $('#feedbackButtonForward').click(function() {
                reloadPins();
                openNextPage();
            });
            $('.sitemapPageLink').click(function () {
                reloadPins();
            });
        } else {
            setTimeout(fireWhenReady, 2000);
        }
    }

    // For local prototypes...
    // Gets the authtoken string from the server and stores it locally. If auth cookie is necessary and not present, then
    // will prompt the user for the prototype password or allows to continue without feedback. 
    // Once the token is successfully retrieved, feedback is loaded.
    function getAuthTokenAndLoad() {
        var listHtml = [];
        listHtml.push(
            '<div class="feedbackAuthDialog">',
            '    <form class="feedbackAuthForm" action="">',
            '        <center>',
            '            This prototype requires a password to enable discussions.<br/><br/>',
            '            Please enter the password or choose to continue without discussions.<br/><br/>',
            '            <div style="display:none; color: red;" id="invalidPass">Incorrect Password,<br/>Please Try Again<br/></div>',
            '            <div style="width: 100%;">',
            '                <div style="height: 100%; width: 71%; float: left;">',
            '                    <input style="float: right; width: 120px; margin-top: 1px;" type="password" title="Enter Password" />',
            '                </div>',
            '                <div style="height: 100%; width: 27%; float: right;">',
            '                    <input style="float: left;" type="submit" value="OK" />',
            '                </div>',
            '            </div>',
            '            <br/><br/>',
            '            <a href="" class="continueNoFeedback">Continue without discussions</a>',
            '        </center>',
            '    </form>',
            '</div>');

        $('#interfaceControlFrame').append(listHtml.join(''));

        $('.feedbackAuthDialog').axDialog();

        $('.continueNoFeedback').click(function () {
            var $parentDialogDiv = $(this).parents('.feedbackAuthDialog');
            $parentDialogDiv.axDialog('hide');

            return false;
        });

        var showFeedbackAuthDialog = function () {
            $('.feedbackAuthDialog input[type="password"]').val('');
            $('.feedbackAuthDialog').axDialog('show');
            $('.feedbackAuthDialog input[type="password"]').focus();
        };

        //Attempts to get the auth token
        //If success, store the token and load the feedback plugin
        //If fail, show the auth dialog
        var getAuth = function () {
            $.ajax({
                type: 'GET',
                url: feedbackServiceUrl + '/GetAuthToken',
                data: {
                    shortcut: $axure.player.settings.projectId
                },
                success: function (response) {
                    if (!response.success && response.redirecturl) {
                        showFeedbackAuthDialog();
                        return;
                    }

                    if (response.success) {
                        authCookieValue = response.authToken;
                        loadPlugin();
                        clearAndLoadEntries(false);
                    }
                },
                dataType: 'jsonp'
            });
        };

        //Executes when the user submits the prototype password
        //If the password is wrong, the auth prompt is redisplayed with an error message
        //If it is correct, then getAuth is executed again to retrieve the token
        $('.feedbackAuthForm').submit(function () {
            var feedbackPass = $(this).find('input[type="password"]').val();

            var $parentDialogDiv = $(this).parents('.feedbackAuthDialog');
            $parentDialogDiv.axDialog('hide');

            $.ajax({
                type: 'GET',
                url: feedbackServiceUrl + '/Authenticate',
                data: {
                    shortcut: $axure.player.settings.projectId,
                    password: feedbackPass
                },
                success: function (response) {
                    if (!response.success) {
                        $parentDialogDiv.find('#invalidPass').show();
                        showFeedbackAuthDialog();
                    } else {
                        //getAuth(); FOR NOW WE AREN'T USING getAuth() (PROB IN IE)
                        authCookieValue = response.authToken;
                        loadPlugin();
                        clearAndLoadEntries(false);
                    }
                },
                dataType: 'jsonp'
            });

            return false;
        });

        //Initial attempt to retrieve the auth token...
        //getAuth();
        //FOR NOW ALWAYS PROMPT! (PROB WITH LOCAL COOKIES IN IE)
        showFeedbackAuthDialog();
    }

    // Include the tokens in the ajaxData object
    function includeTokens(ajaxData, excludeUser) {
        //If the authCookieValue is set (a password-protected local prototype), then send the
        //token as well (because cookies don't always get sent to external domains)
        if (authCookieValue) {
            $.extend(ajaxData, { token: authCookieValue });
        }
        if (!excludeUser && userCookieValue) {
            $.extend(ajaxData, { utoken: userCookieValue });
        }
    }

    // Explicitly updates the height for the feedback
    function updateContainerHeight() {
        $('#feedbackScrollContainer').height(
            $('#feedbackHost').height()
            - $('#feedbackHeader').outerHeight()
            - $('#feedbackNewIssueContainer').outerHeight());
    }

    // Performs a quick client side check to see if the user is allowed to perform changes on a given issue/comment
    function checkModifyPermissions($discElem) {
        var userid = $discElem.attr('userid');

        // If issue/comment was anon, allow anyone to change
        if(userid == null || userid === "") {
            return true;
        }

        // If currently logged and and matches issue/comment creator, allow permission
        if(isUsingAxureAcct && userId === userid) {
            return true;
        }

        return false;
    };

    function checkResolvePermissions($discElem) {
        var userid = $discElem.attr('userid');

        // If issue/comment was anon, allow anyone to resolve
        if (userid == null || userid === "") {
            return true;
        }

        // If currently logged in, allowed to resolve
        if (isUsingAxureAcct) {
            return true;
        }

        return false;
    };

    // Updates the last poll time to be the current UTC time.
    function updateLastPollTime(aspDate) {
        lastPollTime = new Date(parseInt(aspDate.substr(6, aspDate.length - 2))).toISOString();
    };

    /////////////////////////////////////////////////////////////////
    /// Axure Account Utils
    /////////////////////////////////////////////////////////////////

    // TODO: for on prem, we need to use an ajax call directly to share instead of accounts
    // Verify authentication against axure accounts
    function axureAuth(callback) {
        if(window.$axshare != null) {
            $axshare.auth(function(response) {
                if(response.success) {
                    userId = response.userId;
                    userName = response.nickname == null || response.nickname.trim() === ''
                        ? response.userEmail
                        : decodeURIComponent(response.nickname.trim());
                    userEmail = response.userEmail;
                    userProfileImg = response.profileImageUrl;
                    isUsingAxureAcct = true;

                    if(response.authToken != null) {
                        $axshare.setAuthCookie(response.authToken);
                        userCookieValue = response.authToken;
                    }
                } else {
                    if(isSafari()) {
                        var ajaxData = {
                            userId: isUsingAxureAcct ? userId : ""
                        };
                        includeTokens(ajaxData);

                        $.ajax({
                            type: 'GET',
                            url: feedbackServiceUrl + '/safariAuth',
                            data: ajaxData,
                            success: function(response) {
                                if(!response.success) {
                                    isUsingAxureAcct = false;
                                } else {
                                    var profile = response.data.profile[userId];
                                    userName = profile.username == null || profile.username.trim() === ''
                                        ? profile.userEmail
                                        : decodeURIComponent(profile.username.trim());
                                    userEmail = profile.userEmail;
                                    userProfileImg = profile.profileImg;
                                    isUsingAxureAcct = true;

                                    if(callback != null) {
                                        callback(response);
                                    }
                                }
                            },
                            dataType: 'jsonp'
                        });
                    } else {
                        isUsingAxureAcct = false;
                    }
                }

                if(callback != null) {
                    callback(response);
                }
            });
        }
    }

    // TODO: for on prem, we need to use an ajax call directly to share instead of accounts
    // Log into axure accoutns
    function axureLogin(email, password, success, failure, saml) {
        if (window.$axshare != null) {
            password = password === passHint ? "" : password;
            $axshare.login(email, password, false, function (response) {
                if (response.redirecturl !== "" && response.redirecturl != null) {
                    saml(response);

                    return;
                }

                if(response.success && (response.verified || isSubInstance)) {
                    if (isSafari()) {
                        userId = response.userId;
                        userName = response.nickname == null || response.nickname.trim() === '' ? response.userEmail : decodeURIComponent(response.nickname.trim());
                        userEmail = response.userEmail;
                        userProfileImg = response.profileImageUrl;
                        isUsingAxureAcct = true;

                        if (response.authToken != null) {
                            $axshare.setAuthCookie(response.authToken);
                            userCookieValue = response.authToken;
                        }
                    }

                    axureAuth(success);
                } else {
                    failure(response);
                }
            }, window.ON_PREM_LDAP_ENABLED);
        } else {
            failure(response);
        }
    }

    /////////////////////////////////////////////////////////////////
    /// AJAX Utilities
    /////////////////////////////////////////////////////////////////

    // Keeps querying the server for the Cached Response (max of 10 times) and 
    // triggers onFinished functions when successful response is received
    var MAX_RETRIES = 10;
    var getResponse = function (tagVal, retryNum, onFinished, onError) {
        var ajaxData = {
            tag: tagVal,
            shortcut: $axure.player.settings.projectId
        };
        includeTokens(ajaxData, true);

        $.ajax({
            type: 'GET',
            url: feedbackServiceUrl + '/getResponse',
            data: ajaxData,
            success: function (response) {
                if (response && response.success && response.data) {
                    onFinished(tagVal, response.data);
                } else if (response.success) { // if the response succeeded but we didn't get a comment, then we're waiting
                    if (retryNum < MAX_RETRIES) {
                        setTimeout(function () {
                            getResponse(tagVal, retryNum + 1, onFinished, onError);
                        }, 3000);
                    } else {
                        if(onError != null) {
                            onError();
                        }
                    }
                } else if(!response.success && response.redirecturl) {
                    window.location.href = response.redirecturl;
                } 
            },
            dataType: 'jsonp'
        });
    };

    // Executes directly after an ajax post request 
    // if the return data is retrieved, then execute the 'finish' function or execute 
    // getResponse above which queries for a cached response
    var getResponseOrFinish = function (data, tagVal, onFinished, onError) {
        if (!data) {
            getResponse(tagVal, 0, onFinished, onError);
        } else {
            onFinished(tagVal, data);
        }
    };

    // Used in comment create/update/delete and issue create/delete
    // Determines if the prototype is hosted and the browser is IE, in which case it sends
    // the request via GET to overcome the cross-domain limitation in IE
    function sendAjax(postUrl, postData) {
        //if($.browser.msie && document.URL.toLowerCase().indexOf('http://') != -1) {
        $.ajax({
            type: 'GET',
            url: postUrl,
            data: postData,
            dataType: 'jsonp'
        });
        // 3-28-2012: Got rid of cross-domain post for now so that everything is jsonp for SSL
        //} else {
        //    $.ajax({
        //        type: 'POST',
        //        url: postUrl,
        //        data: postData
        //    });
        //}
    };

    /////////////////////////////////////////////////////////////////
    /// Get/Set Anon Commenter name
    /////////////////////////////////////////////////////////////////

    // Sets the commenter name cookie and updates all commenterName fields on the page
    var setCommenterName = function (value, days) {
        //If empty string is given as name, delete the cookie
        if (value.length == 0 || value.toUpperCase() == 'ANONYMOUS') {
            days = -1;
        }

        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            var expires = '; expires=' + date.toGMTString();
        } else {
            var expires = '';
        }

        document.cookie = 'feedbackCommenterName=' + value + expires + '; path=/';
        localCommenterName = value;

        if (days < 0) {
            $('.commenterName').text('Anonymous');
            localCommenterName = null;
        } else {
            $('.commenterName').text(value);
        }
    };

    // Gets the commenter name from the cookie or returns 'Anonymous' if one doesn't exist
    var getCommenterName = function () {
        if (localCommenterName != null) return localCommenterName;

        var cookieVal = getCookieValue('feedbackCommenterName');

        // Return the cookie val else return 'Anonymous' if the cookie doesn't exist
        return cookieVal == null ? 'Anonymous' : cookieVal;
    };

    //---------------------------------------------------------------------------------------------------------------------------
    //----------END OF INTERNAL UTILITY FUNCTIONS--------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------


    //---------------------------------------------------------------------------------------------------------------------------
    //----------ELEMENT SETUP----------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------

    // Given the header table per issue/comment, load the correct commenter img (anon/axure user)
    function loadCommenterImg($feedbackUserHeaderTable, name, imgUrl) {
        var $img = $feedbackUserHeaderTable.find('.commenterImg');

        if(imgUrl != null && imgUrl !== '') {
            $img.css('background-image', ('url("' + imgUrl + '")'));
        } else {
            var initial = (name == null ? (userName == null ? userEmail : userName) : name).trim().toUpperCase().charAt(0);
            $img.css('background-image', '');
            $img.addClass('anonCommenterImg');
            $img.text(initial);
        }
    }

    // Loads and prepares all of the correct information for the commenter in the new issue container.
    // This shows the right links and the correct commenter information
    function loadFeedbackUserInfo($feedbackUserHeaderTable) {
        if (isUsingAxureAcct) {
            var name = userName != null || userName !== '' ? userName : userEmail;
            $feedbackUserHeaderTable.find('.loginOption').hide();
            $feedbackUserHeaderTable.find('.logoutOption').show();
            $feedbackUserHeaderTable.find('.anonFeedbackUserName').hide();
            $feedbackUserHeaderTable.find('.axureFeedbackUserName').text(name);
            $feedbackUserHeaderTable.find('.axureFeedbackUserName').attr('title', name);
            $feedbackUserHeaderTable.find('.axureFeedbackUserName').show();
        } else {
            $feedbackUserHeaderTable.find('.logoutOption').hide();
            $feedbackUserHeaderTable.find('.loginOption').show();
            $feedbackUserHeaderTable.find('.axureFeedbackUserName').hide();
            $feedbackUserHeaderTable.find('.anonNameLink').text(getCommenterName());
            $feedbackUserHeaderTable.find('.anonNameChangeDisplay').text(getCommenterName());
            $feedbackUserHeaderTable.find('.anonFeedbackUserName').show();
        }

        if (isUsingAxureAcct && userProfileImg != null && userProfileImg !== '') {
            var $commenterImgTd = $feedbackUserHeaderTable.find('.commenterImgTd');
            var $commenterImg = $feedbackUserHeaderTable.find('.commenterImg');

            $commenterImgTd.show();
            $commenterImg.css('background-image', ('url("' + userProfileImg + '")'));
            $commenterImg.text('');

            loadCommenterImg($feedbackUserHeaderTable, null, userProfileImg);
        } else {
            if(isUsingAxureAcct) {
                $feedbackUserHeaderTable.find('.commenterImgTd').show();
            } else {
                $feedbackUserHeaderTable.find('.commenterImgTd').hide();
            }

            loadCommenterImg($feedbackUserHeaderTable, isUsingAxureAcct ? null : getCommenterName(), null);
        }
    }

    // Loads and prepares the information for the anonymous commenter name change speech bubble
    function loadAnonNameChangeInfo($bubble, isNewPinIssue) {
        var $commenterImg = $bubble.find('.commenterImg');
        var $anonNameSpan = $bubble.find('.anonNameChangeDisplay');
        var $anonNameInput = $bubble.find('.anonNameChangeInput');
        var currName = getCommenterName();
        var initial = currName.toUpperCase().charAt(0);

        $commenterImg.text(initial);
        $anonNameSpan.text(currName);
        $anonNameInput.val(currName);
    }
    
    //---------------------------------------------------------------------------------------------------------------------------
    //----------END OF ELEMENT SETUP---------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------


    //---------------------------------------------------------------------------------------------------------------------------
    //----------HTML GENERATION--------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------

    // Builds the HTML for adding a new issue without a pin (default method)
    function buildFeedbackUserHeaderHtml(hasPin) {
        return [
            '<table class="feedbackUserHeaderTable">',
            '   <tbody>',
            '       <tr>',
            '           <td class="commenterImgTd">',
            '               <div class="commenterImg"></div>',
                            (hasPin ? '<div class="feedbackUserPin"></div>' : ''),
            '           </td>',
            '           <td class="feedbackUserNameTd">',
            '               <div class="anonFeedbackUserName">',
            '                   Commenting as <a class="anonNameLink"></a>',
            '               </div>',
            '               <div class="axureFeedbackUserName"></div>',
            '           </td>',
            '           <td class="loginOptionsTd">',
                        (isManagePage
                            ? ''
                            : '<a class="loginOption">Log In</a><a class="logoutOption">Log Out</a><span>▼</span>'
                        ),
            '           </td>',
            '       </tr>',
            '   </tbody>',
            '</table>'
        ].join("");
    }

    // For use with side bar only (no issues/comments via pin)
    function buildChangeAnonNameSpeechBubble() {
        return [
            '<div class="axContextSpeechBubbleAbove anonNameChangeBubble">',
            '   <div class="anonNameChangeContainer">',
            '       <table class="anonNameChangeInfo" style="margin-bottom: 10px;">',
            '           <tbody><tr>',
            '               <td class="commenterImgTd" style="padding-right: 0;"><div class="commenterImg anonCommenterImg"></div></td>',
            //'               <td style="width: 75px;">Commenting as</td>',
            '               <td style="width: 5px;"></td>',
            '               <td class="anonNameChangeSection">',
            '                   <span class="anonNameChangeDisplay"></span>',
            '                   <input type="text" class="anonNameChangeInput" />',
            '               </td>',
            '           </tbody></tr>',
            '       </table>',
            //'       <div class="feedbackGreenBtn">LOG IN</div>',
            '   </div>',
            '</div>'
        ].join("");
    }

    // Build the sign up link in the login dialog
    function buildSignUpLinkDivHtml(includeDiv) {
        if(isSubInstance) { return ""; }

        return [
            (!includeDiv ? '' : '<div style="text-align: right; margin-top: 5px; font-size: 10px;">'),
                '<span>No account? <a class="axureSignUpLink" href="', window.AXSHARE_HOST_SECURE_URL, '" target="_blank">Sign Up</a></span>',
            (!includeDiv ? '' : '</div>')
        ].join("");
    }

    // For use with side bar only (no issues/comments via pin)
    function buildLoginSpeechBubble() {
        return [
            '<div class="axClearMsgBubble axureLoginBubble">',
            '   <div class="axureLoginBubbleContainer">',
            '       <span style="font-weight: bold; font-size: 10px;">Login into your Axure Share account</span>',
            '       <input type="text" name="email" class="axureEmail" style="margin-top: 7px;"/>',
            '       <input name="password" class="axurePassword" />',
            '       <div class="feedbackGreenBtn">LOG IN</div>',
            '       <div class="errorMessage"></div>',
                    buildSignUpLinkDivHtml(true),
            '   </div>',
            '</div>'
        ].join("");

    }


    /////////////////////////////////////////////////////////////////
    /// General HTML Gen
    /////////////////////////////////////////////////////////////////

    // Generate the container and div for the camera icon if the browser is screenshot capable
    function genCameraIconDiv() {
        var html = [
            '<div class="textAreaCameraContainer">',
            (window.$axscreenshot != null && $axscreenshot.isScreenshotCompatible && !isMobile()) ? '<div class="cameraIcon screenshotIssueIcon" onclick="executeScreenshot(this)" title="' + ($axscreenshot.isMac ? 'Cmd + Shift + 1' : 'Ctrl + Shift + 1') + '"></div>' : '',
            '</div>'
        ];

        return html.join("");
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML for creating a new Issue.
    /////////////////////////////////////////////////////////////////

    // The html to submit a New Issue.
    var getNewIssueHtml = function () {
        return [
            '<div id="feedbackTag_', $axure.utils.createUniqueTag(), '" class="feedbackEntryItem" style="z-index: 1;">',
            '   <div id="feedbackNewIssueEntry" class="">',
            '       <input type="text" value="', (isLocalChrome() ? 'Add a comment' : 'Or add comment without a location'), '" class="newIssueTextInputMimic" readonly/>',
            '       <div class="newIssueFeedbackEntryContainer">',
                        buildFeedbackUserHeaderHtml(false),
                        buildChangeAnonNameSpeechBubble(),
                        buildLoginSpeechBubble(),
            '           <div class="feedbackEntryTextAreaContainer issueFeedbackEntryTextAreaContainer">',
            '               <textarea id="" class="feedbackEntryTextArea issueFeedbackEntryTextArea" rows="3" maxlength="1000"></textarea>',
                            genCameraIconDiv(),
            '           </div>',
            '       </div>',
            '   </div>',
            '   <div class="feedbackEntryItemLinks" style="display: none;">',
            '       <div class="feedbackEntryItemErrorMessage"></div>',
            '       <a class="feedbackBlueBtn feedbackEntryItemSave">POST</a>',
            '   </div>',
            '</div>'].join("");
    };

    // The HTML for the button to trigger show the layer to add new pins
    var getNewIssuePinHtml = function () {
        return [
            '<div id="feedbackNewIssuePinBtn" class="feedbackPinBtn">',
            '   <div class="addIcon"></div>',
            '   <span>COMMENT ON SCREEN</span>',
            '</div>'
        ].join("");
    };

    // The html for the newly added processing issue
    var buildProcessingIssueHtml = function (feedbackTag) {
        return [
            "<div id='", feedbackTag, "' class='feedbackEntryItem processingIssue'>",
            "   <div class='feedbackEntryTextBoxContainer' style='width: initial; display: block;'>",
            "       <table style='table-layout: fixed; width: 100%;'><tbody><tr>",
            "       <td style='width: 100%;'>",
            "           <input class='feedbackEntryTextBox processingIssueTextBox' type='text' style='padding-right: 10px;' disabled />",
            "       </td>",
            "       <td style='width: 20px; padding-right: 2px;'>",
            "           <img class='textBoxSpinner' src='" + contentUrlPrefix + "/Content/plugins/feedback/styles/images/spinner.gif' style='' title='Processing...' />",
            "       </td>",
            "       </tr></tbody></table>",
            "   </div>",
            "</div>"
        ].join("");
    }


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML for editing an issue
    /////////////////////////////////////////////////////////////////

    // The html for editing issues
    var getEditIssueHtml = function (issueId, origIssueText) {
        return [
            "<div id='feedbackTag_", $axure.utils.createUniqueTag(), "' issueId='", issueId, "' class='feedbackEntryItem editFeedbackEntryItem editIssue'>",
            "       <div class='feedbackEntryTextAreaContainer issueFeedbackEntryTextAreaContainer' style='margin-top: 7px;'>",
            "           <textarea class='feedbackEntryTextArea issueFeedbackEntryTextArea' rows='3' placeholder='Enter your comment here' maxlength='1000'>", origIssueText, "</textarea>",
                        (!$('#rightPanel').length && !$('#outerContainer').length && !$('#leftPanel').length || !$axscreenshot.isScreenshotCompatible) 
                            ? ""
                            : genCameraIconDiv(),
            "           </div>",
            "       </div>",
            "       <div class='feedbackEntryItemLinks'>",
            '           <div class="feedbackEntryItemErrorMessageRow"><div class="feedbackEntryItemErrorMessage"></div></div>',
            '           <a class="feedbackEntryItemCancel" style="float: left;">Cancel</a>',
            '           <a class="feedbackBlueBtn feedbackEntryItemSave">EDIT COMMENT</a>',
            "       </div>",
            "</div>"
        ].join("");
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML for creating a new Comment.
    /////////////////////////////////////////////////////////////////

    // The html for a new create comment container.
    var getNewCommentEntryHtml = function (issueCode) {
        var issue = issueCode ? issueCode : "";

        return [
            '<div id="feedbackTag_', $axure.utils.createUniqueTag(), '" issueCode="', issue, '" class="feedbackEntryItem commentFeedbackEntryItem">',
                buildFeedbackUserHeaderHtml(false),
                buildChangeAnonNameSpeechBubble(),
                buildLoginSpeechBubble(),
            '   <div class="feedbackEntryTextAreaContainer commentFeedbackEntryTextAreaContainer">',
            '       <textarea rows="5" maxlength="1000" class="feedbackEntryTextArea commentFeedbackEntryTextArea" autofocus /><br/>',
                    (!$('#rightPanel').length && !$('#outerContainer').length && !$('#leftPanel').length || !$axscreenshot.isScreenshotCompatible)
                        ? ''
                        : genCameraIconDiv(),
            '   </div>',
            '   <div class="feedbackEntryItemLinks">',
            '       <div class="feedbackEntryItemErrorMessageRow"><div class="feedbackEntryItemErrorMessage"></div></div>',
            '       <a class="feedbackBlueBtn feedbackEntryItemSave">POST REPLY</a>',
            '   </div>',
            '</div>'
        ].join("");
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML for editing a comment
    /////////////////////////////////////////////////////////////////

    // Html for editing comments
    var getEditCommentHtml = function (commentId, origCommentText) {
        return [
            '<div id="feedbackTag_', $axure.utils.createUniqueTag(), '" commentId="', commentId, '" class="feedbackEntryItem editFeedbackEntryItem">',
            '   <div class="feedbackEntryTextAreaContainer commentFeedbackEntryTextAreaContainer">',
            '       <textarea rows="5" maxlength="1000" class="feedbackEntryTextArea commentFeedbackEntryTextArea">' + origCommentText + '</textarea>',
                    (!$('#rightPanel').length && !$('#outerContainer').length && !$('#leftPanel').length || !$axscreenshot.isScreenshotCompatible)
                        ? ''
                        : genCameraIconDiv(),
            '   </div>',
            '   <div class="feedbackEntryItemLinks">',
            '       <div class="feedbackEntryItemErrorMessageRow"><div class="feedbackEntryItemErrorMessage"></div></div>',
            '       <a class="feedbackEntryItemCancel" style="float: left;">Cancel</a>',
            '       <a class="feedbackBlueBtn feedbackEntryItemSave">EDIT REPLY</a>',
            '   </div>',
            '</div>'
        ].join("");;
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML for saved Issues/Comments
    /////////////////////////////////////////////////////////////////

    // Returns the html for the issues passed in.
    // Restores the issue's content and builds containers for all of the controls, inputs, and related comments.
    var getHtmlForIssues = function (issues, profiles, isManagePage, isResolved) {
        var currentDate = new Date();
        var items = issues;
        if (!isArray(issues)) {
            items = [issues];
        }

        var listHtml = [];
        $.each(items, function (index, issue) {
            var profile = profiles != null ? profiles[issue.userId] : null;
            var hasProfile = profile != null;
            var profileName = hasProfile ? (profile.username == null || profile.username.trim() === "" ? profile.userEmail : decodeURIComponent(profile.username)) : issue.creator;
            var hasProfileImage = hasProfile && profile.profileImg != null;
            var hasPin = issue.x != null && issue.y != null && issue.color != null && issue.pinNumber != null;

            // Header and description
            listHtml.push(
                '<div class="feedbackIssue" id="issue_', issue.code, '" isunresolved="', issue.resolvedDate == null, '">'
            );

            if(isResolved) {
                listHtml.push(
                    '   <div class="resolvedHeader">',
                    '       <span>Marked resolved by ', issue.modifier, ' ', buildResolvedDate(issue.modifiedDate), '</span>',
                    '   </div>'
                );
            }

            listHtml.push(
                '   <table class="feedbackUserHeaderTable"><tbody><tr>',
                '       <td class="commenterImgTd">',
                '           <div class="commenterImg ', (!hasProfile || !hasProfileImage ? 'anonCommenterImg' : ''), '" style="', (!hasProfileImage ? '' : ('background-image: url(\'' + profile.profileImg + '\')')) , '">',
                                (!hasProfile || !hasProfileImage ? profileName.charAt(0).toUpperCase() : ''),
                '           </div>',
                            (!hasPin ? '' : '<div class="feedbackUserPin pinColor_' + issue.color + '">' + issue.pinNumber + '</div>'),
                '       </td>',
                '       <td class="feedbackUserNameTd">',
                "           <div class='feedbackUserName' title='", profileName, "'>", profileName, "</div>",
                '       </td>',
                '       <td class="relativeCreatedDateTd">',
                "           <div class='relativeCreatedDate' date='", Date.parse(issue.createdDate), "'>", buildRelativeDate(currentDate, new Date(Date.parse(issue.createdDate))), "</div>",
                '       </td>',
                '   </tr></tbody></table>',
                "<div class='topicDescription' userid='", issue.userId, "'>", issue.description.replace('\n', '<br>'), "</div>"
            );

            // Screenshots
            if (issue.screenshotIds != null && issue.screenshotIds.length > 0) {
                for (var i = 0; i < issue.screenshotIds.length; ++i) {
                    listHtml.push(
                        '   <div class="feedbackItem_thumbnail" id="screenshot_', issue.screenshotIds[i], '">',
                        '       <div class="feedbackItem_thumbnailImg">',
                        '           <img class="thumbnailImg" src="', issue.screenshotUrls[i], '" />',
                        '           <img class="fullsizeImg" src="', issue.fullsizeUrls[i], '" />',
                        '       </div>',
                        '       <div class="preloadDiv">',
                        '       </div>',
                        '   </div>');
                }
            }

            // Footer
            // ▶ OR ► 
            //if (!isManagePage) {
                listHtml.push(
                    "<div class='topicFooter'>",
                    "<div class='topicOptions'>•••</div>",
                    "<div class='showRepliesButton' style='", issue.numComments > 0 ? "" : "display: none;", "'>",
                    "<span class='replyCount'>",
                    issue.numComments,
                    "</span>",
                    "<span class='showReplyText'> ", issue.numComments === 1 ? "Reply" : "Replies", " ▶</span>",
                    "</div>",
                    "<div class='hideRepliesButton'>",
                    "<span>Hide Replies ▼</span>",
                    "</div>",
                    "<a class='replyToggleButton toggleOff' style='", issue.numComments > 0 ? "display: none;" : "", "'>Reply</a>",
                    "</div>"
                );
            //}

            // Nested comments
            listHtml.push(
                '   <div class="issueComments"></div>',
                '</div>'
            );
        });

        var html = listHtml.join('');
        return html;
    };

    // Generates the html for a single comment or list of comments.
    var getHtmlForComments = function (comments, profiles, addReplyButton, isPin, hideMenu) {
        var currentDate = new Date();
        var items = comments;
        if (!isArray(comments)) {
            items = [comments];
        }

        var listHtml = [];
        $.each(items, function (index, item) {
            var profile = profiles != null ? profiles[item.userId] : null;
            var hasProfile = profile != null;
            var profileName = hasProfile ? (profile.username == null || profile.username.trim() === "" ? profile.userEmail : decodeURIComponent(profile.username)) : item.commenter;
            var hasProfileImage = hasProfile && profile.profileImg != null;

            // Header and description
            listHtml.push(
                '<div class="feedbackItem" id="', (isPin ? 'pinComment_' : 'comment_'), item.id, '">',
                '   <table class="feedbackUserHeaderTable"><tbody><tr>',
                '       <td class="commenterImgTd">',
                '           <div class="commenterImg ', (!hasProfile || !hasProfileImage ? 'anonCommenterImg' : ''), '" style="', (!hasProfileImage ? '' : ('background-image: url(\'' + profile.profileImg + '\')')), '">',
                                (!hasProfile || !hasProfileImage ? profileName.charAt(0).toUpperCase() : ''),
                '           </div>',
                '       </td>',
                '       <td class="feedbackUserNameTd">',
                "           <div class='feedbackUserName' title='", item.commenter, "'>", item.commenter, "</div>",
                '       </td>',
                '       <td class="relativeCreatedDateTd">',
                "           <div class='relativeCreatedDate'>",
                                item.commentModifiedDate == null
                                    ? '' + buildRelativeDate(currentDate, new Date(Date.parse(item.commentDate)))
                                    : 'Edited: ' + buildRelativeDate(currentDate, new Date(Date.parse(item.commentModifiedDate))),
                "           </div>",
                '       </td>',
                '   </tr></tbody></table>',
                '   <div class="feedbackItem_commentText" userid="', item.userId, '">', item.commentText.replace(/\n/g, "<br/>"), '</div>');

            // Screenshots
            if (item.screenshotIds != null && item.screenshotIds.length > 0) {
                for (var i = 0; i < item.screenshotIds.length; ++i) {
                    listHtml.push(
                        '   <div class="feedbackItem_thumbnail" id="screenshot_', item.screenshotIds[i], '">',
                        '       <div class="feedbackItem_thumbnailImg">',
                        '           <img class="thumbnailImg" src="', item.screenshotUrls[i], '" style="maxWidth: 90%; maxHeight: 130px; display: block; margin: 0 auto;" />',
                        '           <img class="fullsizeImg" src="', item.fullsizeUrls[i], '" />',
                        '       </div>',
                        '       <div class="preloadDiv">',
                        '       </div>',
                        '   </div>');
                }
            }

            listHtml.push(
                "<div class='feedbackItem_footer'>",
                (hideMenu ? "" : "<div class='topicOptions'>•••</div>"),
                "</div>"
            );

            listHtml.push(
                '</div>'
            );
        });

        if (addReplyButton) {
            listHtml.push(
                "<div class='issueReplyInCommentsDiv'>",
                "   <a class='issueReplyToggleButton toggleOff'>Reply</a>",
                "</div>"
            );
        }

        var html = listHtml.join('');
        return html;
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML for new pin issue and pin comment bubble html
    /////////////////////////////////////////////////////////////////

    // Generate the html for an existing pin
    function existingPinNewCommentHtml(issueCode) {
        var result = [
            "<div class='newPinCommentContainer pinFeedbackEntryItem' id='newPinComment_", $axure.utils.createUniqueTag(), "' issuecode='", issueCode, "'>",
            "   <table class='feedbackUserHeaderTable'><tbody><tr>",
            "       <td class='commenterImgTd'>",
            "           <div class='commenterImg'></div>",
            "       </td>",
            "       <td class='feedbackUserNameTd' style=''>",
            "           <div class='anonFeedbackUserName'>",
            "               <table class='anonNameChangeInfo' style='line-height: 36px;'><tbody><tr>",
            "                   <td style='width: 75px;'>Commenting as</td>",
            "                   <td class='anonNameChangeSection'>",
            "                       <span class='anonNameChangeDisplay'></span>",
            "                       <input type='text' class='anonNameChangeInput' />",
            "                   </td>",
            "               </tbody></tr></table>",
            "           </div>",
            "           <div class='axureFeedbackUserName'></div>",
            "       </td>",
            "       <td class='loginOptionsTd'>",
            "           <a class='loginOption'>Log In</a>",
            "           <a class='logoutOption'>Log Out</a>",
            "           <span>▼</span>",
            "       </td>",
            "   </tr></tbody></table>",
            "   <div class='feedbackEntryTextAreaContainer commentFeedbackEntryTextAreaContainer' style='margin-top: 7px;'>",
            "       <textarea class='feedbackEntryTextArea commentFeedbackEntryTextArea' rows='3' maxlength='1000'></textarea>",
                    genCameraIconDiv(),
            "   </div>",
            "   <div class='feedbackEntryItemLinks' style='margin-bottom: 0;'><div class='feedbackEntryItemErrorMessage' style='line-height: 30px;'></div><div class='feedbackBlueBtn feedbackEntryItemSave newPinCommentPostBtn'>POST</div></div>",
            "</div>"
        ].join("");

        return result;
    };

    // TODO: Remove sign up link for on prem
    // Builds HTML element for when pin click is performed
    function newPinBubbleHtml(tailPosition, isShifted, pinColor, count) {
        var positioning = $axpin.getPinBubblePositioning(tailPosition, isShifted);

        var result = [
            //"<div class='newPinIssueBubble ", positioning.tailClass, "' style='top: ", positioning.bubbleTop, "px; left: ", positioning.bubbleLeft, "px;'>",
            "<div class='newPinIssueBubble axPinContextSpeechBubble' style='top: ", positioning.bubbleTop, "px; left: ", positioning.bubbleLeft, "px;'>",
            "   <div class='newPinIssueScrollContainer'>",
            "   <div class='newPinIssueContainer pinFeedbackEntryItem' id='newPin_", $axure.utils.createUniqueTag(), "'>",
            "       <table class='feedbackUserHeaderTable'><tbody><tr>",
            "           <td class='commenterImgTd'>",
            "               <div class='commenterImg'></div>",
            "               <div class='feedbackUserPin pinColor_", pinColor, "'>", count, "</div>",       // For pin number
            "           </td>",
            "           <td class='feedbackUserNameTd' style=''>",
            "               <div class='anonFeedbackUserName'>",
            "                   <table class='anonNameChangeInfo'><tbody><tr>",
            "                       <td style='width: 75px;'>Commenting as</td>",
            "                       <td class='anonNameChangeSection'>",
            "                           <span class='anonNameChangeDisplay'></span>",
            "                           <input type='text' class='anonNameChangeInput' />",
            "                       </td>",
            "                   </tbody></tr></table>",
            "               </div>",

            "               <div class='axureFeedbackUserName'></div>",
            "           </td>",
            "           <td class='loginOptionsTd'>",
            "               <a class='loginOption'>Log In</a>",
            "               <a class='logoutOption'>Log Out</a>",
            "               <span>▼</span>",
            "           </td>",
            "           <td class='pinColorSelectorTd' align='right'>",
            "               <div class='pinColor_0 pinColorOption'></div>",
            "               <div class='pinColor_1 pinColorOption'></div>",
            "               <div class='pinColor_2 pinColorOption'></div>",
            "               <div class='pinColor_3 pinColorOption'></div>",
            "               <div class='pinColor_4 pinColorOption'></div>",
            "           </td>",
            "       </tr></tbody></table>",
            "       <div class='feedbackEntryTextAreaContainer issueFeedbackEntryTextAreaContainer' style='margin-top: 7px;'>",
            "           <textarea class='feedbackEntryTextArea issueFeedbackEntryTextArea' rows='3' maxlength='1000' placeholder='Enter your comment here'></textarea>",
                            genCameraIconDiv(),
            "       </div>",
            "       <div class='feedbackEntryItemLinks' style='margin-bottom: 0;'><div class='feedbackEntryItemErrorMessage' style='line-height: 28px;'></div><div class='feedbackBlueBtn feedbackEntryItemSave newPinIssuePostBtn'>POST</div></div>",
            "   </div>",
            "   </div>",
            "   <div class='axureLoginContainer'>",
            "       <span style='font-weight: bold; font-size: 10px;'>Login into your Axure Share account</span>",
            "       <input type='text' name='email' class='axureEmail' style='margin-top: 7px;'/>",
            "       <input name='password' class='axurePassword' />",
            "       <div class='feedbackGreenBtn'>LOG IN</div>",
            "       <div class='errorMessage' style='line-height: initial;'></div>",
            "       <div style='text-align: right; margin-top: 5px; font-size: 10px;'>",
            "           <a class='axureCancelLink' style='float: left;'>Cancel</a>",
                        buildSignUpLinkDivHtml(),
            "       </div>",
            "   </div>",
            "</div>"
        ].join("");

        return result;
    };


    //---------------------------------------------------------------------------------------------------------------------------
    //----------END OF HTML GENERATION-------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------


    //---------------------------------------------------------------------------------------------------------------------------
    //----------EVENT BINDING----------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------

    // Shows or hides comment hamburger menu if necessary depending on user state
    function showOrHideMenuOptions(isIssue) {
        // For now, by defult isIssue should be false (only applies to comments)
        $('.issueComments .feedbackItem_footer, .pinIssueComments .feedbackItem_footer').each(function (index, footer) {
            var commentUserId = $(footer).parents('.feedbackItem').find('.feedbackItem_commentText').attr('userid').trim();

            if (commentUserId === "") {
                $(footer).show();
            } else {
                if (isUsingAxureAcct && userId === commentUserId) {
                    $(footer).show();
                } else {
                    $(footer).hide();
                }
            }
        });
    };

    // Gets the most current pin color for the given user and stores it for usage.
    function recallPreviousPinColor() {
        if (window.$axpin == null) { return; }

        var $applicablePins;
        if(isUsingAxureAcct) {
            $applicablePins = $('#feedbackSavedIssues .topicDescription[userid="' + userId + '"]').parents('.feedbackIssue').find('.feedbackUserPin');
        } else {
            $applicablePins = $('#feedbackSavedIssues .topicDescription[userid=""]').parents('.feedbackIssue').find('.feedbackUserPin');
        }

        var $mostRecentDate = findObjectWithLargestAttrValue($applicablePins.parents('.feedbackIssue').find('.relativeCreatedDate'), 'date');

        if ($mostRecentDate == null || $mostRecentDate.length === 0) return;

        var $mostRecent = $mostRecentDate.parents('.feedbackIssue').find('.feedbackUserPin ');
        var pinClass = parseInt($mostRecent.attr('class').match(/[0-9]/g)[0]);      // Caution, this could fail in the future if classes are changed.
        $axpin.getOrSetCurrentPinColorClass(pinClass);

        // If there is a new pin issue in the works, update that color
        $('.newPinIssueBubble').parents('.pin').removeClass().addClass('pin expandedPin').addClass($axpin.getOrSetCurrentPinColorClass());
        $('.newPinIssueBubble .feedbackUserPin').removeClass().addClass('feedbackUserPin').addClass($axpin.getOrSetCurrentPinColorClass());
    };

    // Gets the number that the next pin should take. Mainly used to updated between pages
    function recallNewPinNumber() {
        if (window.$axpin == null) { return; }

        var pinNumbers = $('.pin .feedbackUserPin').contents().filter(function() {
            return this.nodeType === Node.TEXT_NODE;
        }).toArray().map(function (e) { return parseInt(e.nodeValue); });

        //var maxNumber = Math.max(...pinNumbers);
        var maxNumber = Math.max.apply(null, pinNumbers);
        var result = maxNumber === -Infinity ? 1 : ++maxNumber;
        var finalValue = isNaN(result) ? 100 : result;
        $axpin.setCurrPinNumber(finalValue);
    };

    // Hides the text area and show the placeholder
    function hideNewIssueTextArea($feedbackNewIssue) {
        if ($feedbackNewIssue.find('textarea').length === 0) return;

        // Hiding the new issue text area w/out pin
        if ($feedbackNewIssue.find('textarea').val().trim() === ""
            //&& $feedbackNewIssue.find('.newIssueFeedbackEntryContainer').is(':visible')
            && !$('.axureLoginBubble').is(':visible')
            && $('#screenshotPanel').length === 0
            && $('#feedbackNewIssueContainer').find('.feedbackItem_thumbnail').length === 0) {
            $feedbackNewIssue.find('.newIssueFeedbackEntryContainer').hide();
            $feedbackNewIssue.parent().find('.feedbackEntryItemLinks').hide();
            $feedbackNewIssue.parent().find('.newIssueTextInputMimic').show();

            $feedbackNewIssue.find('.feedbackEntryTextArea').attr('placeholder', '');
            $feedbackNewIssue.find('.feedbackentryTextArea').removeClass('errorTextArea');
        }

        // Resize the scroll container to be it's original size 
        updateContainerHeight();
    };

    // Hide all anon name change speech bubbles
    function hideAnonNameSpeechBubble() {
        if($('.anonNameChangeBubble').is(':visible')) {
            $('.anonNameChangeBubble').hide();
        }
    };

    // Hide all Axure login speech bubbles
    function hideAxureLoginSpeechBubble() {
        if($('.axureLoginBubble').is(':visible')) {
            $('.axureLoginBubble').hide();
        }
    };
  
    // Bind events to anon change link and related tools (speech bubble, storing anon name)
    function bindAnonNameChangeEvents($container, isNotPin) {
        var $anonNameLink = $container.find('.anonNameLink');
        var $anonNameBubble = $container.find('.anonNameChangeBubble, .newPinIssueContainer, .newPinCommentContainer'); // For three way use
        var $anonNameSpan = $container.find('.anonNameChangeDisplay');
        var $anonNameInput = $container.find('.anonNameChangeInput');

        // Show the anon name speech bubble
        $anonNameLink.click(function (event) {
            var width = $anonNameLink.width();
            var left = $anonNameLink.position().left;
            var middle = (width / 2) + left;

            // TODO: Will there be other instances of this? during commenting? remove rule or modify?
            // Dynamically add rule to head. This is to be able to modify pseudo element style rules
            $('<style>.anonNameChangeBubble:before{left:' + middle + 'px}</style>').appendTo('head');
            $('<style>.anonNameChangeBubble:after{left:' + middle + 'px}</style>').appendTo('head');

            var linkHeight = $anonNameLink.height();
            var bubbleHeight = $anonNameBubble.height();
            var top = -linkHeight - bubbleHeight;

            $anonNameBubble.css('top', top + 'px');

            // Prepare bubble
            loadAnonNameChangeInfo($anonNameBubble);

            $anonNameSpan.show();
            $anonNameInput.hide();
            $anonNameBubble.show();
            $anonNameSpan.click();
            return false;
        });

        // Show input to allow for anon name change
        $anonNameSpan.click(function (event) {
            $anonNameSpan.hide();
            $anonNameInput.show().focus().select();

            return false;
        });

        // On loss of focus, store contents as anon name and store as a cookie
        $anonNameInput.blur(function (event) {
            var newName = $anonNameInput.val().trim();
            setCommenterName(newName, 7);
            newName = getCommenterName();

            $anonNameBubble.find('.commenterImg').text(newName.toUpperCase().charAt(0));

            $anonNameSpan.text(newName);
            $anonNameLink.text(newName);

            $anonNameSpan.show();
            $anonNameInput.hide();
        });

        // If "Enter" key is pressed, remove focus and perform corresponding events
        $anonNameInput.keyup(function(e) {
            if(e.keyCode === 13) {
                $anonNameInput.blur();
            }
        });

        // Escape key event handler
        $(document).keyup(function (e) {
            if (e.keyCode === 27) {      // esc key
                //e.stopPropagation();

                if(window.$axpin != null) {
                    $axpin.removeAll();
                }
            }
        });
        
        // Log In Button: Switches to axure login bubble
        $anonNameBubble.find('.feedbackGreenBtn').click(function(e) {
            $anonNameBubble.hide();
            $container.find('.loginOption').click();
        });
    };

    // Bind events to axure login speech bubble (watermark, login, errors, click outside)
    function bindAxureLoginContainerEvent($container, isNewPinIssue, isNewPinComment, callback) {
        // Watermark for email and password fields
        $container.find('input[name="email"]').addClass("watermark").val(emailHint).focus(function () {
            if ($(this).val() === emailHint) {
                $(this).removeClass("watermark").val("");
            }
        }).blur(function () {
            if ($(this).val() === "") {
                $(this).addClass("watermark").val(emailHint);
            }

            $container.find('.errorMessage').text('');
            $container.find('.errorMessage').hide();
        }).keyup(function (event) {
            if(event.keyCode == 13) {
                $container.find('.feedbackGreenBtn').click();
            }
        });
        $container.find('input[name="password"]').addClass("watermark").val(passHint).focus(function () {
            if ($(this).val() === passHint) {
                $(this).removeClass("watermark").val("");
                //$(this).removeClass("watermark").val("").attr("type", "password");

                // Note: this might be an issue since jquery doesn't like it. Test in IE
                $(this)[0].setAttribute('type', 'password');
            }
        }).blur(function () {
            if ($(this).val() === "") {
                $(this).val(passHint).addClass("watermark");
                //$(this).val(passHint).addClass("watermark").removeAttr("type");

                // Note: this might be an issue since jquery doesn't like it. Test in IE
                $(this)[0].setAttribute('type', 'text');
            }

            $container.find('.errorMessage').text('');
            $container.find('.errorMessage').hide();
        }).keyup(function (event) {
            if(event.keyCode == 13) {
                $container.find('.feedbackGreenBtn').click();
            }
        });

        // TODO: click out and esc key events

        // Cancel Link Click
        $container.find('.axureCancelLink').click(function(e) {
            $container.hide();
            $container.parent().find('.pinIssueContainer, .newPinIssueScrollContainer, .pinReplyContainer').show();
            $container.parent().show();

            if(callback != null) {
                callback($container.parent(), true);
            }
        });

        // Login Submit Event
        $container.find('.feedbackGreenBtn').click(function (e) {
            var email = $container.find('.axureEmail').val();
            var password = $container.find('.axurePassword').val();

            axureLogin(email, password, function(response) {
                // Success
                loadFeedbackUserInfo(
                    isNewPinIssue
                        ? $container.parent().find('.newPinIssueScrollContainer .feedbackUserHeaderTable')
                        : (isNewPinComment ? $container.parent().find('.pinReplyContainer .feedbackUserHeaderTable') : $container.siblings('.feedbackUserHeaderTable')));

                // Clear out fields
                $container.find('.axureEmail').val(emailHint).addClass("watermark");
                $container.find('.axurePassword').val(passHint).addClass("watermark");
                $container.find('.axurePassword')[0].setAttribute('type', 'text');

                // Hide bubble/container
                if(isNewPinIssue || isNewPinComment) {
                    $container.hide();
                    $container.parent().find('.newPinIssueScrollContainer, .pinIssueContainer, .pinReplyContainer').show();

                    if(callback != null) {
                        callback($container.parent(), false);
                    }
                } else {
                    $container.hide();
                }

                showOrHideMenuOptions();
                recallPreviousPinColor();
            }, function(response) {
                // Failure
                $container.find('.errorMessage').text(response != null && response.message ? response.message : "There was an error connecting to the server, please try again later.");
                $container.find('.errorMessage').show();
            }, function(response) {
                // SAML User
                $container.find('.errorMessage').empty();
                $container.find('.errorMessage').append("Please <a class='refreshLink' style='text-decoration: underline;'>refresh</a> this page after logging in via your identity provider.");
                $container.find('.errorMessage').show();

                window.open(response.redirecturl, '_blank');

                $container.find('.errorMessage').find('.refreshLink').click(function() {
                    location.reload(true);
                });
            });
        });
    };

    // Bind events to login/logout options
    function bindFeedbackUserTableEvents($feedbackUserHeaderTable) {
        $feedbackUserHeaderTable.find('.loginOption').click(function () {
            var $loginBubble = $feedbackUserHeaderTable.siblings('.axureLoginBubble');

            $loginBubble.find('.errorMessage').text('');
            $loginBubble.find('.errorMessage').hide();

            if($('#issueDisplay').length === 0) {
                $loginBubble.css('top', '0');
            } else {
                $loginBubble.css('top', '-100px');
                $loginBubble.css('left', '60px');
            }

            $loginBubble.show();
        });
        $feedbackUserHeaderTable.find('.logoutOption').click(function () {
            function completeLogout() {
                isUsingAxureAcct = false;
                loadFeedbackUserInfo($feedbackUserHeaderTable);
                showOrHideMenuOptions();
                recallPreviousPinColor();

                userId = '';
                userProfileImg = '';
            };

            if(window.$axshare != null) {
                $axshare.logout(completeLogout);
            } else {
                completeLogout();
            }
        });
    };

    // Bind character limit checking to the text area
    function bindTextAreaErrorMessage($container) {
        $container.find('.feedbackEntryTextArea').on('input change paste', function() {
            var text = $(this).val();
            var errMsgDiv = $container.find('.feedbackEntryItemErrorMessage');

            if (text.length >= 1000) {
                $container.find('.feedbackEntryItemErrorMessageRow').show();
                errMsgDiv.text(textAreaCharLimit);
            } else {
                // Case for the new sidebar issue only
                if ($container.parents('#feedbackNewIssue').length !== 0) {
                    errMsgDiv.prependTo($container.find('.feedbackEntryItemLinks'));
                    $container.find('.feedbackEntryItemErrorMessageRow').remove();
                }

                $container.find('.feedbackEntryItemErrorMessageRow').hide();
                errMsgDiv.text('');
            }
        });
    }

    // Binds link between side bar issue and pin. On click of side bar, views and expands pin
    function bindSavedIssueToPinLocate($feedbackIssue) {
        var issueId = $feedbackIssue.attr('id').replace('issue_', '');
        var $target = $feedbackIssue.find('.feedbackUserPin, .commenterImg');

        // Little pin click event: Will locate and expand pin
        $target.click(function() {
            var $pin = $('#pinId_' + issueId);
            if($pin.length == 0) return false;

            //var $iframe = $($('#mainFrame')[0].contentWindow.document.body);
            var $iframe = $($('#mainFrame')[0].contentWindow.document);

            // See if pin is in view, if not, scroll to pin
            if (!checkIfElemWithinScrollView($pin, $iframe, $('#rightPanel'))) {
                var viewHeight = $('#rightPanel').height();
                var viewWidth = $('#rightPanel').width();
                var scrollPositionOfPin = $pin.position();

                // Scroll window (iframe) to scroll location
                $iframe.scrollTop(scrollPositionOfPin.top - (viewHeight / 2));
                $iframe.scrollLeft(scrollPositionOfPin.left - (viewWidth / 2));
            }

            // Expand pin if not expanded already (double click is to bring the pin on top)
            $pin.click();
            //if (!$pin.find('.pinIssueBubble').is(':visible')) {
            //    $pin.click();
            //}
        });
    };

    // Binds link between pin and side bar issue. On click of pin, views on issue
    function bindPinToSavedIssueToPinLocate($savedPin) {
        var issueId = $savedPin.attr('id').replace('pinId_', '');
        var $target = $savedPin.find('.feedbackUserPin, .commenterImg');

        // Little pin click event: Will locate side issue and scroll to it
        $target.click(function () {
            // Show discussions if the pane is not open yet
            if(!$('a[pluginid="feedbackHost"]').hasClass('selected')) {
                $('a[pluginid="feedbackHost"]').click();
            }

            var $savedIssue = $('#issue_' + issueId);
            var $feedbackScrollContainer = $('#feedbackScrollContainer');

            if(!checkIfElemWithinScrollView($savedIssue, $feedbackScrollContainer)) {
                $feedbackScrollContainer.scrollTop($savedIssue[0].offsetTop - $feedbackScrollContainer[0].offsetTop);
            }
        });
    };
 

    /////////////////////////////////////////////////////////////////
    /// Relocating Pin Location Events
    /////////////////////////////////////////////////////////////////

    // Currently Empty. Callback for pin location update success.
    function completeUpdatePinLocation() {
        
    }

    // Performs server call to save position
    function updatePinLocation($pin) {
        var issueId = $pin.attr('id').replace('pinId_', '');
        var x = parseInt($pin.css('left'));
        var y = parseInt($pin.css('top'));

        // Submit Ajax request to save issue and move to different section.
        var ajaxData = {
            pageId: $axure.page.id,
            issueCode: issueId,
            x: x,
            y: y,
            creator: isUsingAxureAcct ? userName : getCommenterName()
        };
        includeTokens(ajaxData, true);

        $.ajax({
            type: 'GET',
            url: feedbackServiceUrl + '/UpdatePinLocation',
            data: ajaxData,
            success: function (response) {
                if (!response.success) {
                    // TODO: Error case
                    return;
                } else {
                    completeUpdatePinLocation();
                }
            },
            dataType: 'jsonp'
        });
    };

    // Binds a given pin to moved and saved.
    function bindPinToBeMoved($pin, isNewPin) {
        var originalPos = $pin.position();

        if(!isMobile()) {
            $pin.mousedown(function(e) {
                if($(e.target).hasClass('pin')) {
                    originalPos = $pin.position();

                    $axpin.setMovingPin($pin, e);
                    $pin.css('z-index', ++$axpin.pinZindex);

                    // Add temporary pin move layer 
                    if(!$('#tempPinMoveDiv').length) $('#existingPinsOverlay').after("<div id='tempPinMoveDiv'></div>");
                    $('#tempPinMoveDiv').css('width', $('#rightPanel').width());
                    $('#tempPinMoveDiv').css('height', $('#rightPanel').height());
                }
            });
            $pin.mouseup(function(e) {
                // Remove temporary pin move layer
                $('#tempPinMoveDiv').remove();

                var newPos = $pin.position();

                $axpin.setMovingPin(null);

                // Commit changes to new location
                if(!isNewPin && (newPos.left !== originalPos.left || newPos.top !== originalPos.top)) {
                    updatePinLocation($pin);
                    $axpin.suppressClickPin = $pin;
                }
            });
            $pin.contextmenu(function(e) {
                $pin.mouseup();
            });
        } else {
            $pin[0].addEventListener('touchstart', function(e) {
                if ($(e.target).hasClass('pin')) {
                    originalPos = $pin.position();

                    $axpin.setMovingPin($pin, e, true);
                    $pin.css('z-index', ++$axpin.pinZindex);

                    // Add temporary pin move layer 
                    if (!$('#tempPinMoveDiv').length) $('#existingPinsOverlay').after("<div id='tempPinMoveDiv'></div>");
                    $('#tempPinMoveDiv').css('width', $('#rightPanel').width());
                    $('#tempPinMoveDiv').css('height', $('#rightPanel').height());
                } 
            }, false);
            $pin[0].addEventListener('touchend', function(e) {
                // Remove temporary pin move layer
                $('#tempPinMoveDiv').remove();

                var newPos = $pin.position();

                $axpin.setMovingPin(null);

                // Commit changes to new location
                if (!isNewPin && (newPos.left !== originalPos.left || newPos.top !== originalPos.top)) {
                    updatePinLocation($pin);
                    $axpin.suppressClickPin = $pin;
                }
            }, false);
            $pin.contextmenu(function (e) {
                // Remove temporary pin move layer
                $('#tempPinMoveDiv').remove();

                var newPos = $pin.position();

                $axpin.setMovingPin(null);

                // Commit changes to new location
                if (!isNewPin && (newPos.left !== originalPos.left || newPos.top !== originalPos.top)) {
                    updatePinLocation($pin);
                    $axpin.suppressClickPin = $pin;
                }
            });
        }
    }


    /////////////////////////////////////////////////////////////////
    /// Updating Pin Color Events
    /////////////////////////////////////////////////////////////////

    // Currently Empty. Call back for pin color update success.
    function completeUpdateExistingPinColor() {};

    // Updates all pin related colors for the given selection and performs server update.
    function updateExistingPinColor($currColorOption, $issueContainer, id) {
        var originalColor = $axpin.getPinColorFromObject($issueContainer.find('.selectedPinColorOption'));
        var newColor = $axpin.getPinColorFromObject($currColorOption);
        $issueContainer.find('.selectedPinColorOption').removeClass('selectedPinColorOption');
        $currColorOption.addClass('selectedPinColorOption');

        // Update feedbackUserPin (both side and pin) and pin color
        $('#pinId_' + id).add($('#issue_' + id)).find('.feedbackUserPin').removeClass(originalColor).addClass(newColor);
        $('#pinId_' + id).removeClass(originalColor).addClass(newColor);

        // Submit Ajax request to save color for pin.
        var ajaxData = {
            pageId: $axure.page.id,
            issueCode: id,
            color: $axpin.getPinColorIndexFromClass(newColor),
            creator: isUsingAxureAcct ? userName : getCommenterName()
        };
        includeTokens(ajaxData, true);

        $.ajax({
            type: 'GET',
            url: feedbackServiceUrl + '/updatepincolor',
            data: ajaxData,
            success: function (response) {
                if (!response.success) {
                    // TODO: Error case
                    return;
                } else {
                    completeUpdateExistingPinColor();
                }
            },
            dataType: 'jsonp'
        });
    };


    /////////////////////////////////////////////////////////////////
    /// Ajax success callback functions. For Issue/Comment
    /////////////////////////////////////////////////////////////////

    // Will copy New Issue text to a new textbox while the request is being processed. 
    // The main TextBox will recieve a new id and be ready for a new topic.
    // The other TextBox will be deleted once request is completed.
    var prepareNewIssueEntry = function ($newIssueEntry, isCleanup, isPin) {
        var $feedbackNewIssue = $('#feedbackNewIssue');

        // Simply cleans up the processing new issues fragments
        if (isCleanup) {
            $newIssueEntry.andSelf('.processingIssue').remove();

            return;
        }

        // Build processing textbox for issues
        var issueText = $newIssueEntry.find(".issueFeedbackEntryTextArea").val();
        var id = $newIssueEntry.attr("id");
        var processingIssueHtml = buildProcessingIssueHtml(id);

        // Copy over text to processing text box
        if (isPin) {
            $feedbackNewIssue.append(processingIssueHtml);
            $('#' + id + '.processingIssue').hide();
        } else {
            $newIssueEntry.after(processingIssueHtml);
        }

        $('#' + id + '.processingIssue').find('input').val(issueText);

        // Clear out new Topic textbox entry and focus
        if(!isPin) {
            $newIssueEntry.attr("id", "feedbackTag_" + $axure.utils.createUniqueTag());
            $newIssueEntry.find('.issueFeedbackEntryTextArea').val("");
            $newIssueEntry.find('.issueFeedbackEntryTextArea').focus(function() {
                $axscreenshot.setKeyboardListener($(this));
            });
            $newIssueEntry.find('.issueFeedbackEntryTextArea').focus();
        }
    };

    // Executes after a successful response for an Add or Update Issue operation
    // Inserts the newlycreated or saved issue into the #feedbackSavedIssues div
    var finishAddOrUpdateIssue = function (tagVal, data) {
        //If we are no longer on the page that this issue belongs to, just return
        if (data.pageId != $axure.page.id)
            return;

        var $issueQuery;
        var issue = fixIssueDatesAndSort(data.issue);
        var $newFeedbackDiv = $('#feedbackTag_' + tagVal);
        var existingIssue = $('#issue_' + data.issue.code);
        var pinExistingIssue = $('#pinId_' + data.issue.code);

        if (existingIssue.length > 0 || $('#feedbackSavedIssues').is('.loading')) {
            // Updating exisiting issue. Will remove the original issue and rebuild sidebar and pins
            var $issueDivs = existingIssue.add(pinExistingIssue);
            $issueDivs.remove();
            $newFeedbackDiv.remove();

            // Build new issue for sidebar and append/bind pin html
            $issueQuery = getIssuesControl(issue, data.profile, isManagePage);     // Need to remove original pin before we can readd & rebind new one

            $(isManagePage ? '#issueDisplay .issueDialogContent' : '#feedbackSavedIssues').prepend($issueQuery);

            // Screenshot Loading Complete Callbacks
            $issueQuery.find('.thumbnailImg').each(function () {
                $(this).load(function () {
                    if(window.$axscreenshot !== undefined) {
                        $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                        $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                    }
                });
            });
        } else {
            //Create a new issue
            $issueQuery = getIssuesControl(issue, data.profile, isManagePage); //for now false, to be changed when implementing admin add-issue

            $('#feedbackSavedIssues').prepend($issueQuery);
            //$axscreenshot.$currFeedbackEntry_keyboard = $('#addTopicInput');
            $axscreenshot.setKeyboardListener($('.issueFeedbackEntryTextAreaContainer'));

            // Displays message to notify users that screenshot functionality is on another browser
            $axscreenshot.checkIfShowScreenshotBrowserMsg($('#feedbackSavedIssues'), !$('#rightPanel'));

            // Screenshot Loading Complete Callbacks
            $issueQuery.find('.thumbnailImg').each(function () {
                $(this).load(function () {
                    $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                    $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                });
            });

            // Delete the pending issue text box
            prepareNewIssueEntry($newFeedbackDiv, true);

            // If there is the "No Discussion" message present, hide it
            if ($('.feedbackIssue').length > 0) {
                $('#feedbackNoDiscussionsDiv').hide();
            }
        }

        // Update scroll container height
        updateContainerHeight();

        if(isManagePage) {
            $('#refreshTable').click();
        }
    };

    // Executes after receiving a successful response for an Add or Update comment operation
    var finishAddOrUpdateComment = function (tagVal, data) {
        //If we are no longer on the page that this comment belongs to, just return
        if (data.pageId != $axure.page.id)
            return;

        // Side bar elements
        var $issue = $('#issue_' + data.comment.issueCode);
        var $comment = $('#comment_' + data.comment.id);
        var newFeedbackDiv = $('#feedbackTag_' + tagVal);

        // Pin elements
        var $pin = $('#pinId_' + data.comment.issueCode);
        var $pinComment = $('#pinComment_' + data.comment.id);
        var $pinIssueContainer = $pin.find('.pinIssueContainer');
        var $pinCommentContainer = $pin.find('.pinIssueComments');
        var $newPinCommentContainer = $('#newPinComment_' + tagVal);

        // Given comment data, build html for single comment block.
        var commentCount = 0;
        var comments = fixCommentDatesAndSort(data.comment);
        var commentQuery = getCommentsControl(comments, data.profile, false);
        var commentQueryPin = getCommentsControl(comments, data.profile, false, true);

        if ($comment.length > 0 || $pinComment.length > 0) {
            // Update exisiting comment with newly edited data. (Edit Mode)
            $comment.replaceWith(commentQuery);
            $pinComment.replaceWith(commentQueryPin);

            // Bind comment popup menu
            bindCommentTopicOptionsPopupMenu(commentQuery);
            bindCommentTopicOptionsPopupMenu(commentQueryPin, true);

            // Screenshot Loading Complete Callbacks
            commentQuery.find('.thumbnailImg').each(function () {
                $(this).load(function () {
                    $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                    $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                });
            });
            commentQueryPin.find('.thumbnailImg').each(function() {
                $(this).load(function () {
                    $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                    $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                });
            });
        } else {
            // New Comment - Add comment to existing issue
            if ($issue.length > 0) {
                // New comment container on side bar
                if (newFeedbackDiv.length !== 0) {
                    // Determine where to add comment text block.
                    var $replyButton = newFeedbackDiv.parent().find('.issueReplyInCommentsDiv');
                    if ($replyButton.length !== 0) {
                        $replyButton.before(commentQuery);
                    } else {
                        newFeedbackDiv.before(commentQuery);
                    }

                    // Bind comment popup menu
                    bindCommentTopicOptionsPopupMenu(commentQuery);

                    // Screenshot Loading Complete Callbacks
                    commentQuery.find('.thumbnailImg').each(function () {
                        $(this).load(function () {
                            $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                            $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                        });
                    });
                }

                // Update Issue's Reply button
                commentCount = $issue.find('.feedbackItem').length;
                if (commentCount > 1) {
                    // Turn off toggle for button to show comment box.
                    $issue.find('.issueReplyToggleButton').removeClass('toggleOn').addClass('toggleOff');
                } else {
                    // Update the "Reply" button for the issue. Show's the Show/Hide buttons instead of just 'Reply'
                    $issue.find('.topicFooter .replyToggleButton').hide();
                    $issue.find('.hideRepliesButton').show();

                    // Bind the new 'Reply' Button
                    $issue.find('.issueComments').append("<div class='issueReplyInCommentsDiv'><a class='issueReplyToggleButton toggleOff'>Reply</a></div>");
                    bindReplyButton($issue.find('.issueComments'));
                }

                // Update the Show Reply count
                $issue.find(".replyCount").text($issue.find('.feedbackItem').length).change();

                // Remove comment text box
                newFeedbackDiv.remove();
            }

            // New Comment on Pin
            if ($pin.length > 0) {
                // New comment container on pin
                if($newPinCommentContainer.length !== 0) {
                    // Determine where to add comment text block.
                    var $inlineReplyBtn = $pinCommentContainer.find('.issueReplyInCommentsDiv');
                    if ($inlineReplyBtn.length !== 0) {
                        $inlineReplyBtn.before(commentQueryPin);
                    } else {
                        $pinCommentContainer.append(commentQueryPin);
                    }

                    // Bind comment popup menu
                    bindCommentTopicOptionsPopupMenu(commentQueryPin, true);

                    // Screenshot Loading Complete Callbacks
                    commentQueryPin.find('.thumbnailImg').each(function () {
                        $(this).load(function () {
                            $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                            $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                        });
                    });
                }
             
                // Update Issue's Reply Button
                commentCount = $pinCommentContainer.find('.feedbackItem').length;
                if(commentCount > 1) {
                    // Turn off toggle for button to show comment box.
                    $pinIssueContainer.find('.issueReplyToggleButton').removeClass('toggleOn').addClass('toggleOff');
                } else {
                    // Update the "Reply" button for the issue. Show's the Show/Hide buttons instead of just 'Reply'
                    $pinIssueContainer.find('.topicFooter .replyToggleButton').hide();
                    if ($newPinCommentContainer.length !== 0) { $pinIssueContainer.find('.hideRepliesButton').show(); }

                    // Bind the new 'Reply' Button
                    $pinCommentContainer.find('.issueReplyInCommentsDiv').remove();
                    if($pin.find('.hideRepliesButton').length !== 0 && $pin.find('.hideRepliesButton').is(':visible')) {
                        $pinIssueContainer.find('.pinIssueComments').append('<div class="issueReplyInCommentsDiv"><a class="issueReplyToggleButton toggleOff">Reply</a></div>');
                    }
                    bindReplyButton($pinIssueContainer.find('.pinIssueComments'), true);
                }

                // Update the 'Show Reply' btn count -> main issue here is doing a cross update
                $pinIssueContainer.find('.replyCount').text($pinCommentContainer.find('.feedbackItem').length).change();

                // Remove working comment text box
                $newPinCommentContainer.parents('.pinReplyContainer').hide();
                $newPinCommentContainer.remove();
                $pinIssueContainer.parent().scrollTop($pinIssueContainer.height());
            }

            // Make sure both sidebar and pin comment counts are synced
            var $sidebarCount = $issue.find('.replyCount');
            var $pinCount = $pinIssueContainer.find('.replyCount');
            if ($sidebarCount.text().trim() !== $pinCount.text().trim()) {
                if (newFeedbackDiv.length === 0) {
                    // Case where comment was added from pin
                    $sidebarCount.text($pinCount.text()).change();

                    if(commentCount <= 1) {
                        // Case where the reply box was open but pinning comment was submitted -> remove reply box
                        if($issue.find('.commentFeedbackEntryItem').length > 0) {
                            $issue.find('.commentFeedbackEntryItem').remove();
                        }

                        // Update the "Reply" button for the issue. Show's the Show/Hide buttons instead of just 'Reply'
                        $issue.find('.topicFooter .replyToggleButton').hide();
                        $issue.find('.showRepliesButton').show();
                        $issue.find('.hideRepliesButton').click().hide();
                    } else {
                        // Force reload: Keeping the same view but will reloaded comments
                        $issue.find('.showRepliesButton').click();
                    }
                } else {
                    // Case where comment was added from sidebar
                    $pinCount.text($sidebarCount.text()).change();

                    // Case where the reply box was open but pinning comment was submitted -> remove reply box
                    if ($pinIssueContainer.find('.newPinCommentContainer').length > 0) {
                        $pinIssueContainer.find('.newPinCommentContainer').remove();
                    }

                    if (commentCount <= 1) {
                        // Update the "Reply" button for the issue. Show's the Show/Hide buttons instead of just 'Reply'
                        $pinIssueContainer.find('.topicFooter .replyToggleButton').hide();
                        $pinIssueContainer.find('.showRepliesButton').show();
                    } else {
                        // Force reload: Keeping the same view but will reloaded comments
                        $pinIssueContainer.find('.showRepliesButton').click();

                    }
                }
            }
        }

        if(isManagePage) {
            $('#refreshTable').click();
        }
    };

    // Executes after receiving a successful response for an add or update issue op for pins
    var finishAddPinIssue = function(tagVal, data) {
        //If we are no longer on the page that this issue belongs to, just return
        if (data.pageId != $axure.page.id) { return; }

        var $newFeedbackDiv = $('#newPin_' + tagVal);
        var existingIssue = $('#issue_' + data.issue.code);

        // This will load the issue into the side bar as well as load the pin
        var issue = fixIssueDatesAndSort(data.issue);
        var issueQuery = getIssuesControl(issue, data.profile, false);

        $('#feedbackSavedIssues').prepend(issueQuery);
        $axscreenshot.setKeyboardListener($('.issueFeedbackEntryTextAreaContainer'));

        // Displays message to notify users that screenshot functionality is on another browser
        $axscreenshot.checkIfShowScreenshotBrowserMsg($('#feedbackSavedIssues'), !$('#rightPanel'));

        // Screenshot Loading Complete Callbacks
        issueQuery.find('.thumbnailImg').each(function () {
            $(this).load(function () {
                $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                $axscreenshot.screenshotBindToDiv($(this).parent().parent());
            });
        });

        // Delete the pending issue text box
        prepareNewIssueEntry($newFeedbackDiv, true);

        // Remove the temporary pin and expand permanent pin
        $('.savedPin .pinIssueBubble:visible').click();
        $axpin.publishPin(tagVal, data.issue.code);

        // Update pin number
        $axpin.setCurrPinNumber(issue[0].pinNumber + 1);

        // If there is the "No Discussion" message present, hide it
        if ($('.feedbackIssue').length > 0) {
            $('#feedbackNoDiscussionsDiv').hide();
        }
    };


    /////////////////////////////////////////////////////////////////
    /// Shared event handlers
    /////////////////////////////////////////////////////////////////

    // Event handler for when "Cancel" is clicked for a new issue or comment action
    var newIssueOrCommentCancelEvent = function ($replyContainer, isPin) {
        //var parent = $(this).parents('.feedbackEntryItem');

        $replyContainer.hide();
        var $parent = $replyContainer.find(isPin ? '.newPinCommentContainer' : '.feedbackEntryItem');
        if($parent.length === 0) return false;

        var $newEntries = $parent.parents('#feedbackNewIssueContainer');
        //var $newEntries = $issueContainer.parent().siblings('#feedbackNewIssueContainer');

        var isReply = ($newEntries.length === 0);
        if (isReply) {
            //$parent.parents('.feedbackIssue').find('.editIssueLinks').show();
            $parent.remove();
        } else {
            $parent.find('input[type=text]').val('');
        }

        // Remove any screenshots attached to New Issue. Comments taken care when whole div is removed.
        $newEntries.siblings('.feedbackItem_thumbnail').remove();

        // Removes the screenshot overlay and any related attributes
        if(window.$axscreenshot !== undefined) {
            $axscreenshot.toggleTextBoxScreenshotSpinner(false);
            $axscreenshot.isRequestingScreenshot = false;
            $axscreenshot.removeAll();
        }

        return false;
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for managing Saved Issues.
    /////////////////////////////////////////////////////////////////

    // Builds, binds, and shows the options popup (meatball menu) for a given issue.
    var bindTopicOptionsPopupMenu = function ($issueContainer, isPin) {
        if ($issueContainer.length === 0) return;

        var $topicOptions = $issueContainer.find(".topicOptions");
        var id = isPin ? $issueContainer.parents('.savedPin').attr('id').replace('pinId_', '') : $issueContainer.attr('id').replace('issue_', '');
        var hasPin = isPin || $issueContainer.find('.feedbackUserPin').length !== 0;

        // Showing popup
        $topicOptions.click(function () {
            var isUnresolved = $('#issue_' + id).attr('isunresolved') === 'true';
            var $elem = $issueContainer.find('.topicDescription, .pinTopicDescription');
            var canModify = checkModifyPermissions($elem);
            var canResolve = checkResolvePermissions($elem)
            var popup = [];

            popup.push(
                "<div class='axContextSpeechBubbleBottomAdaptive issueOptions' id='topicOptions_", id, "'>",
                "   <div class='popupLinksDiv issueOptionLinksDiv'>"
            );

            if (isUnresolved) {
                if (hasPin) {
                    popup.push(
                        "<div class='popupColorSelector'>",
                        "   <div class='pinColor_0 pinColorOption'></div>",
                        "   <div class='pinColor_1 pinColorOption'></div>",
                        "   <div class='pinColor_2 pinColorOption'></div>",
                        "   <div class='pinColor_3 pinColorOption'></div>",
                        "   <div class='pinColor_4 pinColorOption'></div>",
                        "</div>"
                    );
                }

                if (canModify) {
                    popup.push(
                        "<a class='popupLink editIssue'>Edit Comment</a>",
                        "<a class='popupLink deleteIssue'>Delete Comment</a>",
                    );
                }
                if (canResolve) {
                    popup.push("<a class='popupLink resolveIssue'>Mark Resolved</a>")
                }

            } else {
                if (canResolve) {
                    popup.push("<a class='popupLink unresolveIssue'>Mark Unresolved</a>");
                }
            }

            popup.push("</div>");
            if(canModify) {
                popup.push(
                    "   <div id='feedbackTag_", $axure.utils.createUniqueTag(),"' class='popupLinksDiv issueDeleteConfirmDiv' style='display: none;'>",
                    "       <p>Are you sure?</p>",
                    "       <div>",
                    "           <a class='issueDeleteCancel popupLink' href='#'>Cancel</a>",
                    "           <div class='issueDeleteConfirm feedbackRedBtn' href='#'>Delete</div>",
                    "       </div>",
                    "   </div>"
                );
            }
            popup.push("</div>");

            $issueContainer.append(popup.join(""));

            if(hasPin && window.$axpin != null) {
                var pinClass = $axpin.getPinColorFromObject($issueContainer.find('.feedbackUserPin'));
                $issueContainer.find('.pinColorOption.' + pinClass).addClass('selectedPinColorOption');

                // Color selection click
                $issueContainer.find('.pinColorOption').click(function () {
                    updateExistingPinColor($(this), $issueContainer, id);

                    $issueContainer.find('.issueOptions').remove();
                });
            }

            var $popup = $issueContainer.find(".issueOptions");
            var position = $topicOptions.position();
            $popup.css('left', isPin ? "0px" : position.left - ($topicOptions.width() / 2) + 6 + "px");
            $popup.css('top', position.top - $popup.height() - 13 + "px");
            $popup.show();

            // Edit link binding
            $popup.find('.editIssue').click(function() {
                var $feedbackItem = $popup.parents('.feedbackIssue, .savedPin');
                var issueId = $feedbackItem.attr('id').replace(isPin ? 'pinId_' : 'issue', '');
                //var originalIssue = $feedbackItem.find('.topicDescription, .pinTopicDescription').text();
                var originalIssue = $feedbackItem.find('.topicDescription, .pinTopicDescription').html().replace('<br>', '\n');

                var $editIssueControl = getEditIssueControl(issueId, originalIssue);

                $popup.hide();
                $feedbackItem.find('.topicOptions').hide();
                $feedbackItem.find('.topicDescription, .pinTopicDescription').hide();
                $feedbackItem.find('.topicDescription, .pinTopicDescription').after($editIssueControl);

                // TODO: Hide commented on date and state that it is currently being edited.
                //$feedbackItem.find('.feedbackItem_commentDate').hide();
                //$feedbackItem.find('.feedbackItem_commentDate').after("<div></div>");

                // Text Area focus and event handler
                $feedbackItem.find('.feedbackEntryTextArea').focus(function () {
                    if(window.$axscreenshot !== undefined) {
                        $axscreenshot.setKeyboardListener($(this));
                        //$currFeedbackEntry_keyboard = $(this);
                    }
                });
                $feedbackItem.find('.feedbackEntryTextArea').focus();

                // Add Delete Icons and Events to Screenshot Thumbnails
                $feedbackItem.find('.feedbackItem_thumbnail').each(function () {
                    // TODO: we want to allow deleting screenshots
                    if(window.$axscreenshot !== undefined) {
                        $axscreenshot.bindEditModeToThumbnail($(this));
                    }
                });
            });

            // Delete Link binding
            $popup.find('.deleteIssue').click(function() {
                var $link = $popup.find('.issueOptionLinksDiv');
                var $options = $popup.find('.issueDeleteConfirmDiv');

                // Show ConfirmDiv 
                $link.hide();
                $options.show();

                // Binds the Cancel and Delete button
                $popup.find('.issueDeleteCancel').click(function() {
                    $options.hide();
                    $link.show();
                });
                
                // Performs delete confirm event
                $popup.find('.issueDeleteConfirm').click(function() {
                    deleteIssue($issueContainer, isPin);
                });
            });

            // Resolve/Unresolve link binding
            $popup.find('.resolveIssue').click(function() { resolveIssue(id, true); });
            $popup.find('.unresolveIssue').click(function() { resolveIssue(id, false); });

            // Hiding the popup: Scroll
            $(isPin ? $issueContainer.parents('.savedPin').find('.pinIssueScrollContainer') : $('#feedbackScrollContainer')).scroll(function(event) {
                $popup.remove();
            });

            // Hiding the popup: Click
            $(document).mouseup(function(event) {
                if(!$popup.is(event.target) && $popup.has(event.target).length === 0) {
                    $popup.remove();
                }
            });
        });
    };

    // Binds Reply/ViewReplies/HideReplies functionality for a given issue container.
    var bindViewCommentsAndReplyButtons = function ($issueContainer, isPin, isResolved) {
        if($issueContainer.length === 0) return;

        var $showReplies = $issueContainer.find('.showRepliesButton');
        var $hideReplies = $issueContainer.find('.hideRepliesButton');
        var $toggleReply = $issueContainer.find('.replyToggleButton');
        var $commentsDiv = $issueContainer.find('.issueComments, .pinIssueComments');
        var issueCode = isPin ? $issueContainer.parents('.savedPin').attr('id').replace('pinId_', '') : $issueContainer.attr('id').replace('issue_', '');

        // Show reply button click event
        $showReplies.click(function () {
            // Expand comments 
            // Determine to open reply box and focus on text area immediately.
            var jumpToAddBox = false;
            if ($(this).text().indexOf('add') !== -1) {
                jumpToAddBox = true;
            }

            loadFeedbackForIssue($commentsDiv, issueCode, jumpToAddBox, isPin);
            $commentsDiv.show();

            // Update Buttons
            $showReplies.hide();
            $hideReplies.show();
        });

        // Hide reply button click event
        $hideReplies.click(function () {
            // Collapse comments
            $issueContainer.find('.issueComments, .pinIssueComments').hide();

            // Remove any new comment containers (mainly for pins)
            if(isPin) {
                var $replyContainer = $issueContainer.parents('.pinIssueBubble').children('.pinReplyContainer');
                newIssueOrCommentCancelEvent($replyContainer, isPin);
            }

            // Update Buttons
            $hideReplies.hide();
            $showReplies.show();
        });

        // When the number of replies change for a given issue.
        $showReplies.find('.replyCount').change(function (event) {
            var value = $showReplies.find('.replyCount').text();

            if (value === "1") {
                $showReplies.find('.showReplyText').text(" Reply ▶");
            } else {
                $showReplies.find('.showReplyText').text(" Replies ▶");
            }
        });

        // Toggle reply click event
        $toggleReply.click(function () {
            // change color
            if($(this).hasClass("toggleOff")) {
                $(this).addClass("toggleOn");
                $(this).removeClass("toggleOff");

                // show new feedbagTag_ div. Contains text area, camera button, post button
                if(isPin) {
                    var $replyContainer = $issueContainer.parents('.pinIssueBubble').children('.pinReplyContainer');
                    $replyContainer.append(existingPinNewCommentHtml(issueCode));
                    $replyContainer.show();
                    existingPinNewCommentEventBinding($replyContainer.find('.newPinCommentContainer'));
                } else {
                    issueNewCommentLink_click($issueContainer);
                }

                $issueContainer.find('.issueComments, .pinIssueComments').show();

            } else {
                $(this).addClass("toggleOff");
                $(this).removeClass("toggleOn");

                // Hide contents
                if(isPin) {
                    newIssueOrCommentCancelEvent($issueContainer.parents('.pinIssueBubble').children('.pinReplyContainer'), isPin);
                } else {
                    newIssueOrCommentCancelEvent($issueContainer.find('.issueComments'), isPin);
                }
                $issueContainer.find('.issueComments, .pinIssueComments').hide();
            }
        });

        // Determine which of the three buttons to show
        $showReplies.hide();
        $hideReplies.hide();
        $toggleReply.hide();
        if (parseInt($showReplies.find('.replyCount').text()) > 0) {
            $showReplies.show();
        } else {
            if(!isResolved) {
                $toggleReply.show();
            }
        }
    };

    // Creates the html for issues, creates a jquery object for them, and attaches the necessary events.
    var getIssuesControl = function (issues, profiles, isManagePage, isResolved, excludePin) {
        var dom = $(getHtmlForIssues(issues, profiles, isManagePage, isResolved));

        //if (!isManagePage) {
            // Expand/collapse issue's comments.
            dom.find('.issueComments').hide();

            // Bind buttons and functionality for a given issue
            dom.each(function (index, issue) {
                bindTopicOptionsPopupMenu($(issue));
                bindViewCommentsAndReplyButtons($(issue), false, isResolved);
                bindSavedIssueToPinLocate($(issue));
            });

            // Manage loading, no discussion msg, and issues container visibility
            $('#feedbackLoadingPlaceholder').hide();
            if (issues.length > 0) {
                $('#feedbackScrollContainer').show();
            } else {
                if(!isResolved) {
                    $('#feedbackNoDiscussionsDiv').show();
                }
            }

            // Pin management section
            if(isManagePage === false && !excludePin) {
                // Loads exisiting pin issues onto the screen
                $.each(issues, function (index, value) {
                    if (!isLocalChrome() && value.x != null && value.y != null) {
                        $axpin.loadPin(value, profiles, isResolved, existingPinHtml, existingPinEventBinding);
                    }
                });

                // Determine most recent color to be used for user and current pin number
                $.each(issues, function(index, value) {
                    if(value.x != null && value.y != null && value.color != null && value.pinNumber != null) {
                        if (window.$axpin != null && $axpin.getCurrPinNumber() < value.pinNumber + 1) {
                            $axpin.setCurrPinNumber(value.pinNumber + 1);
                        }
                    }
                });

                // Bind issue screenshot events and click to focus
                $('.savedPin').each(function() {
                    $axscreenshot.screenshotFinishLoadingCallback($(this));
                    $axscreenshot.screenshotBindToDiv($(this));
                    bindPinToSavedIssueToPinLocate($(this));
                });
            }

        return dom;
    };


    /////////////////////////////////////////////////////////////////
    /// Resolve/Unresolve Event Handlers
    /////////////////////////////////////////////////////////////////

    // On success, finishes issue resolution on client side
    function completeResolveIssue(data) {
        var $feedbackIssue = $('#issue_' + data.issue.code);
        var $pinFeedbackIssue = $('#pinId_' + data.issue.code);
        var $feedbackSavedIssues = $('#feedbackSavedIssues');
        var $resolvedIssueDiv = !isManagePage ? $('#feedbackResolvedIssuesDiv') : $feedbackIssue.parents('.issueDialogContent');;

        var sortedIssues = fixIssueDatesAndSort(data.issue);
        var $issueQuery = getIssuesControl(sortedIssues, data.profile, isManagePage, true, true);
        $resolvedIssueDiv.append($issueQuery);
        $feedbackIssue.remove();

        $pinFeedbackIssue.find('.issueOptions').remove();
        $pinFeedbackIssue.addClass('resolvedPin');

        if(!$('#hideResolvedIssuesBtn').is(':visible')) $('#showResolvedIssuesBtn').show();

        // TODO: If there are no more discussion elements in saved, show no discussion div
        if($feedbackSavedIssues.children() === 0) {
            $('#feedbackNoDiscussionsDiv').show();
        }

        // TODO: On completion, remove issue (sidebar only. For pin, change the pin color)

        // Screenshot Loading Complete Callbacks
        $issueQuery.find('.thumbnailImg').each(function () {
            $(this).load(function () {
                if (window.$axscreenshot !== undefined) {
                    $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                    $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                }
            });
        });
    };

    // On success, finishes issues de-resolution on client side
    function completeUnresolveIssue(data) {
        var $feedbackIssue = $('#issue_' + data.issue.code);
        var $pinFeedbackIssue = $('#pinId_' + data.issue.code);
        var $feedbackSavedIssues = !isManagePage ? $('#feedbackSavedIssues') : $feedbackIssue.parents('.issueDialogContent');;
        var $resolvedIssueDiv = $('#feedbackResolvedIssuesDiv'); 

        var sortedIssues = fixIssueDatesAndSort(data.issue);
        var $issueQuery = getIssuesControl(sortedIssues, data.profile, isManagePage, false, true);
        $feedbackSavedIssues.prepend($issueQuery);
        $feedbackIssue.remove();

        $pinFeedbackIssue.find('.issueOptions').remove();
        $pinFeedbackIssue.removeClass('resolvedPin');

        if ($resolvedIssueDiv.children().not('#hideResolvedIssuesBtn').not('.feedbackDivider').length === 0) {
            $('#hideResolvedIssuesBtn').click();
            $('#showResolvedIssuesBtn').hide();
        }

        if ($feedbackSavedIssues.children() !== 0) {
            $('#feedbackNoDiscussionsDiv').hide();
        }

        // Screenshot Loading Complete Callbacks
        $issueQuery.find('.thumbnailImg').each(function () {
            $(this).load(function () {
                if (window.$axscreenshot !== undefined) {
                    $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                    $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                }
            });
        });
    };

    // Performs the server call to resolve/unresolve issue
    function resolveIssue(issueId, isResolve) {
        var $feedbackIssue = $('#issue_' + issueId);
        $feedbackIssue.find('.issueOptions').remove();
        $feedbackIssue.append('<div class="feedbackIssue_processing"></div>');

        // Submit Ajax request to save issue and move to different section.
        var ajaxData = {
            pageId: $axure.page.id,
            issueCode: issueId,
            userId: isUsingAxureAcct ? userId : '',
            creator: isUsingAxureAcct ? userName : getCommenterName(),
            isResolve: isResolve
        };
        includeTokens(ajaxData);

        $.ajax({
            type: 'GET',
            url: feedbackServiceUrl + '/resolveissue',
            data: ajaxData,
            success: function (response) {
                if (!response.success) {
                    // TODO: Error case
                    return;
                } else {
                    if (isResolve) {
                        completeResolveIssue(response.data);
                    } else {
                        completeUnresolveIssue(response.data);
                    }
                }
            },
            dataType: 'jsonp'
        });
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for managing Saved Comments
    /////////////////////////////////////////////////////////////////

    // Builds, binds, and shows the options popup (meatball menu) for a given issue.
    var bindCommentTopicOptionsPopupMenu = function ($commentContainer, isPin) {
        if ($commentContainer.attr("id") == null || $commentContainer.parents('.savedPin, .feedbackIssue').length === 0) { return; }

        var $topicOptions = $commentContainer.find(".topicOptions");
        //var id = isPin ? $commentContainer.parents('.savedPin').attr('id').replace('pinId_', '') : $commentContainer.attr("id").replace("comment_", "");
        var id = isPin
            ? $commentContainer.parents('.savedPin').attr('id').replace('pinId_', '')
            : $commentContainer.parents('.feedbackIssue').attr("id").replace("issue_", "");
       
        // Showing popup
        $topicOptions.click(function () {
            var isUnresolved = $('#issue_' + id).attr('isunresolved') === 'true';
            var isAuth = checkModifyPermissions($commentContainer.find('.feedbackItem_commentText'));
            var popup = [];

            popup.push(
                "<div class='axContextSpeechBubbleBottomAdaptive commentOptions' id='topicOptions_", id, "'>",
                "   <div class='popupLinksDiv commentOptionLinksDiv'>"
            );

            if (isUnresolved) {
                if (isAuth) {
                    popup.push(
                        "         <a class='popupLink editComment'>Edit Reply</a>",
                        "         <a class='popupLink deleteComment'>Delete Reply</a>"
                    );
                }
            } 

            popup.push("</div>");
            if (isAuth) {
                popup.push(
                    "   <div id='feedbackTag_", $axure.utils.createUniqueTag(), "' class='popupLinksDiv commentDeleteConfirmDiv' style='display: none;'>",
                    "       <p>Are you sure?</p>",
                    "       <div>",
                    "           <a class='commentDeleteCancel popupLink' href='#'>Cancel</a>",
                    "           <div class='commentDeleteConfirm feedbackRedBtn' href='#'>Delete</div>",
                    "       </div>",
                    "   </div>"
                );
            }
            popup.push("</div>");

            $commentContainer.append(popup.join(""));

            var $popup = $commentContainer.find(".commentOptions");
            var position = $topicOptions.position();
            $popup.css('left', position.left - ($topicOptions.width() / 2) + (isPin ? -2 : 6) + "px");
            $popup.css('top', position.top - $popup.height() - 13 + "px");
            $popup.show();

            // Edit Link Binding
            $popup.find('.editComment').click(function() {
                var $feedbackItem = $popup.parent();
                var commentId = $feedbackItem.attr('id').replace('comment_', '');
                //var originalComment = $feedbackItem.find('.feedbackItem_commentText').text();
                var originalComment = $feedbackItem.find('.feedbackItem_commentText').html().replace('<br>', '\n');
                var $editCommentControl = getEditCommentControl(commentId, originalComment);

                $popup.hide();
                $feedbackItem.find('.feedbackItem_footer').hide();
                $feedbackItem.find('.issueReplyInCommentsDiv').hide();
                $feedbackItem.find('.feedbackItem_commentText').hide();
                $feedbackItem.find('.feedbackItem_commentText').after($editCommentControl);

                // TODO: Hide commented on date and state that it is currently being edited.
                //$feedbackItem.find('.feedbackItem_commentDate').hide();
                //$feedbackItem.find('.feedbackItem_commentDate').after("<div></div>");

                // Text Area focus and event handler
                $feedbackItem.find('.feedbackEntryTextArea').focus(function () {
                    if(window.$axscreenshot !== undefined) {
                        $axscreenshot.setKeyboardListener($(this));
                        //$currFeedbackEntry_keyboard = $(this);
                    }
                });
                $feedbackItem.find('.feedbackEntryTextArea').focus();

                // Add Delete Icons and Events to Screenshot Thumbnails
                $feedbackItem.find('.feedbackItem_thumbnail').each(function () {
                    if(window.$axscreenshot !== undefined) {
                        $axscreenshot.bindEditModeToThumbnail($(this));
                    }
                });
            });

            // Delete Link binding
            $popup.find('.deleteComment').click(function() {
                var $link = $popup.find('.commentOptionLinksDiv');
                var $options = $popup.find('.commentDeleteConfirmDiv');

                // Show ConfirmDiv 
                $link.hide();
                $options.show();

                // Binds the Cancel and Delete button
                $popup.find('.commentDeleteCancel').click(function() {
                    $options.hide();
                    $link.show();
                });

                $popup.find('.commentDeleteConfirm').click(function() {
                    deleteComment($commentContainer, isPin);
                });
            });

            // Hiding the popup: Scroll
            $('#feedbackHost').scroll(function(event) {
                $popup.remove();
            });

            // Hiding the popup: Click
            $(document).mouseup(function(event) {
                if(!$popup.is(event.target) && $popup.has(event.target).length === 0) {
                    $popup.remove();
                }
            });
        });
    };

    // Binds the functionality for the Reply toggle button in Comments
    var bindReplyButton = function ($commentContainer, isPin) {
        var $toggleReply = $commentContainer.find('.issueReplyToggleButton');
        var $issueContainer = $commentContainer.parents(isPin ? '.pinIssueContainer' : '.feedbackIssue');
        var $replyContainer = isPin ?  $issueContainer.parents('.pinIssueBubble').children('.pinReplyContainer') : $issueContainer;

        var issueCode = isPin ? $replyContainer.parents('.savedPin').attr('id').replace('pinId_', '') : $replyContainer.attr('id').replace('issue_', '');

        $toggleReply.click(function () {
            // change color
            if ($(this).hasClass("toggleOff")) {
                $(this).addClass("toggleOn");
                $(this).removeClass("toggleOff");

                // show new feedbagTag_ div. Contains text area, camera button, post button
                if(isPin && $replyContainer.find('.newPinCommentContainer').length === 0) {
                    $replyContainer.append(existingPinNewCommentHtml(issueCode));
                    $replyContainer.show();
                    existingPinNewCommentEventBinding($replyContainer.find('.newPinCommentContainer'));
                } else if(!isPin) {
                    issueNewCommentLink_click($replyContainer);
                } else {
                    $replyContainer.find('textarea').val('');
                    $replyContainer.show();
                }
                $commentContainer.find('.issueComments, .pinIssueComments').show();

                // TODO: Scroll to object
                // TODO: focus on textbox
            } else {
                $(this).addClass("toggleOff");
                $(this).removeClass("toggleOn");

                // Hide contents
                if(isPin) {
                    newIssueOrCommentCancelEvent($replyContainer);
                } else {
                    newIssueOrCommentCancelEvent($issueContainer.find('.commentFeedbackEntryItem'));
                }
                $commentContainer.find('.issueComments, .pinIssueComments').hide();
            }
        });
    };

    // Generates the html for a list of comments (or a single comment), creates a jquery object
    // and attaches the necessary events. The comments should all belong to the same issue
    // and will be created without the div for the surrounding issue
    var getCommentsControl = function (comments, profiles, addReplyButton, isPin, hideMenu) {
        var $dom = $(getHtmlForComments(comments, profiles, addReplyButton, isPin, hideMenu));

        return $dom;
    };

    // Bind the Reply button for cases where comments already exist
    var bindCommentsControlPostAppend = function ($dom, isPin) {
        // Bind each individual comment options popup
        $dom.each(function (index, comment) {
            bindCommentTopicOptionsPopupMenu($(comment), isPin);
        });

        // Bind single reply button
        $dom.find('.issueReplyToggleButton').click(function () {
            var $commentContainer = $dom;
            var $issueContainer = $commentContainer.parents(isPin ? '.pinIssueContainer' : '.feedbackIssue');
            var $replyContainer = isPin ? $issueContainer.parents('.pinIssueBubble').children('.pinReplyContainer') : $issueContainer;

            // change color
            if ($(this).hasClass("toggleOff")) {
                $(this).addClass("toggleOn");
                $(this).removeClass("toggleOff");

                // show new feedbagTag_ div. Contains text area, camera button, post button
                if(isPin) {
                    $replyContainer.append(existingPinNewCommentHtml());
                    $replyContainer.show();
                    existingPinNewCommentEventBinding($replyContainer.find('.newPinCommentContainer'));
                } else {
                    issueNewCommentLink_click($replyContainer);
                }
                $commentContainer.find('.issueComments, .pinIssueComments').show();

            } else {
                $(this).addClass("toggleOff");
                $(this).removeClass("toggleOn");

                // Hide contents
                newIssueOrCommentCancelEvent(isPin ? $replyContainer : $issueContainer.find('.commentFeedbackEntryItem'), isPin);
                $commentContainer.find('.issueComments, .pinIssueComments').hide();
            }
        });
    }


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for creating a new Issue.
    /////////////////////////////////////////////////////////////////

    // Finalizes create a new issue in the sidebar (w/out pin)
    var completeFeedbackNewIssueCreate = function ($thisObj) {
        var $parent = $thisObj.parents('.feedbackEntryItem');
        var $input = $parent.find('textarea');

        if ($parent.find('a').prop('disabled')) { return false; }

        // Send GUIDS not the data url! - Images to be notified to be saved.
        var submittedImgs = [];
        //$parent.parent().siblings('.feedbackItem_thumbnail').each(function () {
        $('#feedbackNewIssue').find('.feedbackItem_thumbnail').each(function () {
            if ($(this).is(':visible')) {
                var idTag = $(this).attr('id');
                submittedImgs.push(idTag.substring(idTag.indexOf('_') + 1));
            }
        });

        // Disable input, camera, and submit button
        //disableNewIssueEntry(parent, true);

        // Update Issue Container and build temporary processing issue.
        var tagVal = $parent.attr('id').replace('feedbackTag_', '');
        var text = $.trim($input.val());
        prepareNewIssueEntry($parent, false);

        // Remove all screenshot and preview panels
        $axscreenshot.removeAll();
        updateContainerHeight();

        // Submit Ajax request to save issue and move to different section.
        var ajaxData = {
            tag: tagVal,
            shortcut: $axure.player.settings.projectId,
            pageId: $axure.page.id,
            pageName: $axure.page.pageName,
            userId: isUsingAxureAcct ? userId : '',
            creator: isUsingAxureAcct ? userName : getCommenterName(),
            description: text,
            images: JSON.stringify(submittedImgs),
            success: function (data) {
                $('#feedbackNewIssue').find('.feedbackSavedIssues, .feedbackItem_thumbnail').each(function () {
                    $(this).remove();
                });

                getResponseOrFinish(data, tagVal, finishAddOrUpdateIssue, function() {
                    // Error message
                    if ($('#feedbackNewIssue').find('.feedbackEntryItemErrorMessageRow').length === 0) $('#feedbackNewIssue').find('.feedbackEntryItemLinks').prepend("<div class='feedbackEntryItemErrorMessageRow'></div>");
                    $('#feedbackNewIssue').find('.feedbackEntryItemErrorMessageRow').append($('#feedbackNewIssue').find('.feedbackEntryItemErrorMessage'));
                    $('#feedbackNewIssue').find('.feedbackEntryItemErrorMessageRow').show();
                    $('#feedbackNewIssue').find('.feedbackEntryItemErrorMessage').text(textAreaLengthError);

                    // Repopulate text area
                    $input.val(text);
                    $input.select();

                    // Remove pending textbox
                    $('#feedbackTag_' + tagVal).remove();

                    // Update scroll container height
                    updateContainerHeight();
                });
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/add', ajaxData);
    };

    // New Issue (w/out pin) POST btn click. Performs basic check prior to continuing to actual submission
    var newIssueCreate_click = function () {
        if ($('#feedbackSavedIssues .preloadDiv').length) { return false; }

        if (readOnlyMode) {
            alert(readOnlyMessage);
            return false;
        }

        // Verify that there is text OR screenshots taken
        var $input = $(this).parents('.feedbackEntryItem').find('textarea');
        var inputText = $.trim($input.val());
        var hasImages = $('#feedbackNewIssue').find('.feedbackItem_thumbnailImg').length > 0;

        if (inputText === '') {
            $input.attr('placeholder', 'Please enter text here');
            $input.addClass('errorTextArea');
            $input.focus();

            return false;
        }

        // TODO: Update for new profile auth - Add auth for when adding new issues
        completeFeedbackNewIssueCreate($(this));

        return false;
    };

    // "Enter" Key version of newIssueCreate_click(). Runs when key pressed inside text area.
    var newIssueCreate_shiftEnter = function (event) {
        if (event.which === 13 && event.shiftKey && !isMobile()) {
            if (readOnlyMode) {
                alert(readOnlyMessage);
                return false;
            }

            // Verify that there is text OR screenshots taken
            var hasImages = $('#feedbackNewIssue').find('.feedbackItem_thumbnailImg').length > 0;
            var inputText = $.trim($(this).val());

            if(inputText === '') {
                $(this).attr('placeholder', 'Please enter text here');
                $(this).addClass('errorTextArea');
                $(this).focus();

                return false;
            }

            // TODO: Update for new profile auth - Add auth for when adding new issues
            completeFeedbackNewIssueCreate($(this));

            return false;
        }
    };

    // On clicking the textbox, will hide itself and show text area for new issue input
    function newIssueTextInputMimic_click(event) {
        var $newIssueEntry = $(this).parent();
        var $newIssueContainer = $newIssueEntry.find('.newIssueFeedbackEntryContainer');

        $(this).hide();
        $(this).parents('.feedbackEntryItem').find('.feedbackEntryItemLinks').show();
        $newIssueContainer.show();
        $newIssueContainer.find('textarea').focus();
        $newIssueContainer.find('textarea').val('');

        loadFeedbackUserInfo($newIssueContainer.find('.feedbackUserHeaderTable'));
        updateContainerHeight();
    };

    // Builds the html for a new Issue Controller and binds the elements to functions
    var getNewIssueControl = function () {
        // add the area for a new issue entry box
        var $newIssueQuery = $(getNewIssueHtml());

        // find the submit, textbox, and textarea elements and bind the corresponding events
        $newIssueQuery.find('.feedbackEntryItemSave').click(newIssueCreate_click);
        $newIssueQuery.find('textarea').keydown(newIssueCreate_shiftEnter);
        $newIssueQuery.find('.newIssueTextInputMimic').click(newIssueTextInputMimic_click);

        // Bind general functions for setting the profile and logout/login
        bindFeedbackUserTableEvents($newIssueQuery.find('.feedbackUserHeaderTable'));
        bindAnonNameChangeEvents($newIssueQuery.find('.newIssueFeedbackEntryContainer'), true);
        bindAxureLoginContainerEvent($newIssueQuery.find('.axureLoginBubble', false));
        bindTextAreaErrorMessage($newIssueQuery);

        return $newIssueQuery;
    };

    // Builds and binds the HTML for adding new pins
    var getNewIssuePinControl = function () {
        // Build html for new issue pin button
        var $newIssuePinQuery = $(getNewIssuePinHtml());

        // Bind the button to perform required actions
        //$newIssuePinQuery.find('#feedbackNewIssuePinBtn').click(feedbackNewIssuePinCreateEvent);
        $($newIssuePinQuery[0]).click(feedbackNewIssuePinCreateEvent);
        //bindPinToSavedIssueToPinLocate();

        // Return html to be appended into the document
        return $newIssuePinQuery;
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for editing an issue.
    /////////////////////////////////////////////////////////////////

    // Performs client side view as changes are submitted
    var completeEditIssueOkLink = function($thisObj) {
        //if ($('#feedbackSavedIssues .preloadDiv').length) { return; }

        var isPin = $thisObj.parents('.savedPin').length !== 0;
        var $parent = isPin ? $thisObj.parents('.savedPin') : $thisObj.parents('.feedbackIssue');
        var issueId = $parent.attr('id').replace(isPin ? 'pinId_' : 'issue_', '');
        var input = $parent.find('textarea');

        var $sbIssue = $('#issue_' + issueId);
        var $pin = $('#pinId_' + issueId);
        var hasPin = $pin.length !== 0;

        // Send GUIDS not the data url!
        var submittedImgs = [];
        $parent.find('.feedbackItem_thumbnail').each(function () {
            if ($(this).is(':visible')) {
                var idTag = $(this).attr('id');
                submittedImgs.push(idTag.substring(idTag.indexOf('_') + 1));
            }
        });

        // Add an overlay over the the text area and show the spinner 
        var text = $.trim(input.val());
        $parent.find('textarea').attr('disabled', 'disabled');
        $sbIssue.append('<div class="addingCommentOverlay"><img class="addingCommentSpinner"/></div>');
        if (hasPin) $pin.append('<div class="addingCommentOverlay"><img class="addingCommentSpinner"/></div>');

        // Adding overlay either to the entire container
        var $sbOverlay = $sbIssue.find('.addingCommentOverlay');
        var $pinOverlay = $pin.find('.addingCommentOverlay');
        var $pinBubble = $pin.find('.pinIssueBubble');

        if ($parent.find('.feedbackItem_thumbnail:visible').length > 0) {
            var height = $sbIssue.height() - $sbIssue.find('.feedbackItem_header').height();
            $sbOverlay.css('top', (-1 * $sbIssue.height() - 10) + 'px');
            $sbOverlay.css('left', '-6px');
            $sbOverlay.css('margin-bottom', -1 * height + 'px');
            $sbOverlay.width($sbIssue.outerWidth() + 6);
            $sbOverlay.height(height);

            if (hasPin) {
                if ($pinBubble.is(':visible')) {
                    // Overlay entire speech bubble
                    $pinOverlay.css('top', '-36px');
                    $pinOverlay.css('left', $pinBubble.position().left + 'px');
                    $pinOverlay.height($pinBubble.outerHeight());
                    $pinOverlay.width($pinBubble.outerWidth());
                } else {
                    // Overlay the pin
                    $pinOverlay.width($pin.width());
                    $pinOverlay.height($pin.height());
                    $pinOverlay.css('border-radius', $pin.css('border-radius'));
                    $pinOverlay.css('top', '-' + $pin.height() + 'px');
                }
            }
        } else {
            if($sbIssue.find('.editFeedbackEntryItem').length !== 0) {
                $sbOverlay.css('top', -1 * ($sbIssue.find('.topicFooter').outerHeight() + $sbIssue.find('.feedbackEntryItemLinks').outerHeight() + $sbIssue.find('.editFeedbackEntryItem').outerHeight() + 13) + 'px');
                $sbOverlay.width($sbIssue.find('.editFeedbackEntryItem').outerWidth() + 2);
                $sbOverlay.height($sbIssue.find('.editFeedbackEntryItem').outerHeight());
            } else {
                $sbOverlay.css('top', -1 * $sbIssue.outerHeight() + 'px');
                $sbOverlay.width($sbIssue.width() + 6);
                $sbOverlay.css('left', '-6px');
                $sbOverlay.height($sbIssue.outerHeight());
            }
            $sbOverlay.css('margin-bottom', -1 * ($sbOverlay.outerHeight()) + 'px');

            if(hasPin) {
                if($pinBubble.is(':visible')) {
                    if($pin.is($parent)) {
                        // Overlay text box
                        $pinOverlay.css('top', "10px");
                        $pinOverlay.css('left', parseInt($pinBubble.css('left')) + 11 + 'px');
                        $pinOverlay.width($parent.find('.editFeedbackEntryItem').outerWidth());
                        $pinOverlay.height($parent.find('.editFeedbackEntryItem').outerHeight());
                    } else {
                        // Overlay entire speech bubble
                        $pinOverlay.css('top', '-36px');
                        $pinOverlay.css('left', $pinBubble.position().left + 'px');
                        $pinOverlay.height($pinBubble.outerHeight());
                        $pinOverlay.width($pinBubble.outerWidth());
                    }
                } else {
                    // Overlay the pin
                    $pinOverlay.width($pin.width());
                    $pinOverlay.height($pin.height());
                    $pinOverlay.css('border-radius', $pin.css('border-radius'));
                    $pinOverlay.css('top', '-' + $pin.height() + 'px');
                }
            }
        }

        // Disable buttons (links)
        $parent.find('.feedbackEntryItemLinks a').each(function () {
            $(this).css('pointer-events', 'none');
        });

        if(window.$axscreenshot !== undefined) {
            $axscreenshot.removeAll();
        }

        var tagVal = $parent.find('.editFeedbackEntryItem').attr('id').replace('feedbackTag_', '');

        var ajaxData = {
            tag: tagVal,
            pageId: $axure.page.id,
            issueId: issueId,
            creator: isUsingAxureAcct ? userName : getCommenterName(),
            userId: isUsingAxureAcct ? userId : '',
            issueText: text,
            shortcut: $axure.player.settings.projectId,
            images: JSON.stringify(submittedImgs),
            success: function (data) {
                getResponseOrFinish(data, tagVal, finishAddOrUpdateIssue, function() {
                    // Error message
                    if ($parent.find('.feedbackEntryItemErrorMessageRow').length === 0) $parent.find('.feedbackEntryItemLinks').prepend("<div class='feedbackEntryItemErrorMessageRow'></div>");
                    $parent.find('.feedbackEntryItemErrorMessageRow').append($parent.find('.feedbackEntryItemErrorMessage'));
                    $parent.find('.feedbackEntryItemErrorMessageRow').show();
                    $parent.find('.feedbackEntryItemErrorMessage').text(textAreaLengthError);

                    // Enable buttons
                    $parent.find('.feedbackEntryItemLinks a').each(function () {
                        $(this).css('pointer-events', 'all');
                    });

                    // Select text
                    $parent.find('textarea').prop('disabled', false);
                    $parent.find('textarea').select();

                    // Remove loading overlay
                    $sbOverlay.remove();
                    $pinOverlay.remove();
                });
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/updateissue', ajaxData);
    };

    // Pre-submission check for submitting changes to issue
    var editIssueOkLink = function() {
        if (readOnlyMode) {
            alert(readOnlyMessage);
            return false;
        }

        var textArea = $(this).parents('.pinIssueContainer, .feedbackIssue').find('.issueFeedbackEntryTextArea');
        var inputText = textArea.val();
        var trimmedText = $.trim(inputText);

        // If there is no text and no new images OR
        // if there is no text and deleted all images(no remaining images via delete during edit)
        if (trimmedText == '' && ((!textArea.parents('.feedbackIssue, .savedPin').find('.feedbackItem_thumbnail').length)
            || !textArea.parents('.feedbackIssue, .savedPin').find('.feedbackItem_thumbnail').is(':visible'))) {
            textArea.val('');
            return false;
        }

        if ($.browser.msie && document.URL.toLowerCase().indexOf('http://') != -1) {
            var encodedText = encodeURI(trimmedText);
            if (encodedText.length > 1600) {
                alert('Comment text is too long, please split into multiple comments.');
                return false;
            }
        }

        completeEditIssueOkLink($(this));

        return false;
    };

    // Restores the issue state back to it's default view. 
    var editIssueCancelLink = function() {
        //var $parent = $('#issueDisplay').length === 0
        //    ? $(this).parents('.feedbackEntryItem')
        //    : $(this).parent().siblings('.feedbackEntryItem');       // On manage page, feedbackEntryItem is a sibling
        var $parent = $(this).parents('.feedbackEntryItem').add($(this).parent().siblings('.feedbackEntryItem'));
        var $feedbackItem = $(this).parents('.feedbackIssue, .savedPin');

        // Remove Delete Icons and Unhide Thumbnails Prepare for Deletion
        $parent.parent().find('.feedbackItem_thumbnail').each(function () {
            // TODO: We want to be able to remove the edit state for screenshots on manage page
            if(window.$axscreenshot !== undefined) {
                $axscreenshot.removeEditModeFromThumbnail($(this));
            }
        });

        // Restore default state
        $feedbackItem.find('.topicOptions').show();
        $feedbackItem.find('.topicDescription, .pinTopicDescription').show();
        $(this).parents('.feedbackEntryItemLinks').remove();
        $parent.remove();

    };

    // Gets the html for editing an issue and binds it's options (Save and cancel)
    var getEditIssueControl = function(issueId, origIssueText) {
        // Build the area for editing an entry
        var $editIssueQuery = $(getEditIssueHtml(issueId, origIssueText));

        // Bind Save and Cancel Buttons
        $editIssueQuery.find('.feedbackEntryItemSave').click(editIssueOkLink);
        $editIssueQuery.find('.feedbackEntryItemCancel').click(editIssueCancelLink);

        // Bind text area error message
        bindTextAreaErrorMessage($editIssueQuery);
        
        return $editIssueQuery;
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for creating a new Comment.
    /////////////////////////////////////////////////////////////////

    // TODO: Fix - Spinner location
    // Performs the actual submission to server for a new comment along with callback for server response
    var completeFeedbackNewCommentSubmitButton_click = function ($thisObj) {
        //if ($('#feedbackSavedIssues .preloadDiv').length) { return; }

        var $parent = $thisObj.parents('.feedbackEntryItem');
        var issueCode = $parent.attr('issueCode');

        // we want to re-enable the reply link
        $parent.parent().find('.editIssueLinks').show();

        var input = $parent.find('textarea');

        // Send GUIDS not the data url!
        var submittedImgs = [];
        //parent.parent().find('.feedbackItem_thumbnail').each(function () {
        $parent.find('.feedbackItem_thumbnail').each(function () {
            if ($(this).is(':visible')) {
                var idTag = $(this).attr('id');
                submittedImgs.push(idTag.substring(idTag.indexOf('_') + 1));
            }
        });

        // Add an overlay over the the text area and show the spinner 
        var text = $.trim(input.val());
        $parent.find('textarea').attr('disabled', 'disabled');
        $parent.append('<div class="addingCommentOverlay"><img class="addingCommentSpinner"/></div>');

        var $overlay = $parent.find('.addingCommentOverlay');
        var $textAreaContainer = $parent.find('.feedbackEntryTextAreaContainer');
        var $links = $parent.find('.feedbackEntryItemLinks');

        // Adding overlay either to the entire container or just the text box.
        if (submittedImgs.length > 0) {
            $overlay.css('top', (-1 * $parent.height() - 10) + 'px');
            $parent.css('margin-bottom', -1 * $parent.outerHeight() + 'px');
            $overlay.width($parent.width() + 2);
            $overlay.height($parent.height());
        } else {
            var top = $links.outerHeight() + $textAreaContainer.height() + 10;
            $overlay.css('top', -1 * top + 'px');
            $overlay.css('margin-bottom', -1 * ($textAreaContainer.outerHeight()) + 'px');
            $overlay.width($textAreaContainer.width() + 2);
            $overlay.height($textAreaContainer.height());
        }

        // Disable buttons (links)
        $parent.find('.feedbackEntryItemLinks a').each(function () {
            $(this).css('pointer-events', 'none');
        });

        // Remove all screenshot tools
        if(window.$axscreenshot !== undefined) { $axscreenshot.removeAll(); }

        var tagVal = $parent.attr('id').replace('feedbackTag_', '');

        var ajaxData = {
            tag: tagVal,
            issueCode: issueCode,
            pageId: $axure.page.id,
            userId: isUsingAxureAcct ? userId : '',
            commenter: isUsingAxureAcct ? userName : getCommenterName(),
            commentText: text,
            shortcut: $axure.player.settings.projectId,
            images: JSON.stringify(submittedImgs),
            success: function (data) {
                getResponseOrFinish(data, tagVal, finishAddOrUpdateComment, function() {
                    // Error message
                    if ($parent.find('.feedbackEntryItemErrorMessageRow').length === 0) $parent.find('.feedbackEntryItemLinks').prepend("<div class='feedbackEntryItemErrorMessageRow'></div>");
                    $parent.find('.feedbackEntryItemErrorMessageRow').append($parent.find('.feedbackEntryItemErrorMessage'));
                    $parent.find('.feedbackEntryItemErrorMessageRow').show();
                    $parent.find('.feedbackEntryItemErrorMessage').text(textAreaLengthError);

                    // Select text
                    input.prop('disabled', false);
                    input.select();

                    // Remove loading overlay
                    $overlay.remove();
                });
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/addcomment', ajaxData);
    };

    // Performs presubmission check for new comments
    var feedbackNewCommentSubmitButton_click = function () {
        if (readOnlyMode) {
            alert(readOnlyMessage);
            return false;
        }

        //if ($('#feedbackSavedIssues .preloadDiv').length > 0) {
        //    return false;
        //}

        // Obtain comment text
        var $input = $(this).parents('.feedbackEntryItem').find('textarea');
        var inputText = $input.val();
        var trimmedText = $.trim(inputText);

        // Verify text
        var hasImages = $input.parents('.feedbackEntryItem').find('.feedbackItem_thumbnail').length;
        if ((trimmedText === "" || inputText === replyReminderString) && !hasImages) {
            $input.val(replyReminderString);
            //textArea.select();
            $input.addClass('errorWatermark');

            return false;
        }

        if ($.browser.msie && document.URL.toLowerCase().indexOf('http://') !== -1) {
            var encodedText = encodeURI(trimmedText);
            if (encodedText.length > 1600) {
                alert('Comment text is too long, please split into multiple comments.');
                return false;
            }
        }

        // First check for the commenter's name, then complete the new comment submission process.
        //showCommenterNameDialogDiv(completeFeedbackNewCommentSubmitButton_click, $(this));
        completeFeedbackNewCommentSubmitButton_click($(this));

        return false;
    };

    // Build the html for a new Comment Controller and binds the elements
    var getNewCommentEntryControl = function (issueCode) {
        // add the area for a new comment entry box
        var $newEntryQuery = $(getNewCommentEntryHtml(issueCode));

        // find the submit and cancel links and attach the click events
        $newEntryQuery.find('.feedbackEntryItemSave').click(feedbackNewCommentSubmitButton_click);
        $newEntryQuery.find('.feedbackEntryItemCancel').click(newIssueOrCommentCancelEvent);

        // Bind watermarking for textarea
        $newEntryQuery.find('.feedbackEntryTextArea').addClass('watermark').val(replyReminderString).focus(function() {
            if($(this).val() === replyReminderString) {
                $(this).removeClass("watermark errorWatermark").val("");
            }
        }).blur(function() {
            if($(this).val() === "") {
                $(this).addClass("watermark").val(replyReminderString);
            }
        });

        // Bind text area error message
        bindTextAreaErrorMessage($newEntryQuery);

        return $newEntryQuery;
    };

    // Executes when user clicks the "Reply" button inside an issue
    // Adds, binds, and focuses the new comment objects
    var issueNewCommentLink_click = function ($issueContainer) {
        var issueCode = $issueContainer.attr('id').replace('issue_', '');

        var $commentEntryControl = getNewCommentEntryControl(issueCode);
        $issueContainer.find('.issueComments').append($commentEntryControl);

        // Bind general functions for setting the profile and logout/login
        bindFeedbackUserTableEvents($commentEntryControl.find('.feedbackUserHeaderTable'));
        bindAnonNameChangeEvents($commentEntryControl, true);
        bindAxureLoginContainerEvent($commentEntryControl.find('.axureLoginBubble'), false);
        loadFeedbackUserInfo($commentEntryControl.find('.feedbackUserHeaderTable'));
    
        // Text Area focus and event handler
        $commentEntryControl.find('.commentFeedbackEntryTextArea').focus(function () {
            if(window.$axscreenshot !== undefined) {
                $axscreenshot.setKeyboardListener($(this));
                //$currFeedbackEntry_keyboard = $(this);
            } 
        });
        $commentEntryControl.find('.commentFeedbackEntryTextArea').focus();

        return false;
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for editing a comment.
    /////////////////////////////////////////////////////////////////

    // TODO: Fix - Spinner location
    // Events for preparing html and events up to and including edit submission
    var completeEditCommentOkLink_click = function ($thisObj) {
        //if ($('#feedbackSavedIssues .preloadDiv').length) { return; }

        var $parent = $thisObj.parents('.feedbackEntryItem');
        var commentId = $parent.attr('commentId').replace('pinComment_', '');       // Only pins will have to do the replacement
        var input = $parent.find('textarea');

        // Send GUIDS not the data url!
        var submittedImgs = [];
        $parent.parent().find('.feedbackItem_thumbnail').each(function () {
            if ($(this).is(':visible')) {
                var idTag = $(this).attr('id');
                submittedImgs.push(idTag.substring(idTag.indexOf('_') + 1));
            }
        });

        // Add an overlay over the the text area and show the spinner 
        var text = $.trim(input.val());
        $parent.find('textarea').attr('disabled', 'disabled');
        $parent.append('<div class="addingCommentOverlay"><img class="addingCommentSpinner"/></div>');

        var $overlay = $parent.find('.addingCommentOverlay');
        var $feedbackItem = $parent.parents('.feedbackItem');

        // Adding overlay over the entire comment
        // TODO: Cover entire comment feed if visible 

        $overlay.width($feedbackItem.width());
        $overlay.height($feedbackItem.outerHeight() + 6);
        $overlay.css('top', -1 * ($feedbackItem.height() - $overlay.height()) + 'px');
        $overlay.css('margin-bottom', -1 * ($overlay.outerHeight()) + 'px');

        // Disable buttons (links)
        $parent.find('.feedbackEntryItemLinks a').each(function () {
            $(this).css('pointer-events', 'none');
        });

        if(window.$axscreenshot !== undefined) { $axscreenshot.removeAll(); }

        var tagVal = $parent.attr('id').replace('feedbackTag_', '');

        var ajaxData = {
            tag: tagVal,
            pageId: $axure.page.id,
            commentId: commentId,
            commenter: isUsingAxureAcct ? userName : getCommenterName(),
            userId: isUsingAxureAcct ? userId : '',
            commentText: text,
            shortcut: $axure.player.settings.projectId,
            images: JSON.stringify(submittedImgs),
            success: function (data) {
                getResponseOrFinish(data, tagVal, finishAddOrUpdateComment, function() {
                    // Error message
                    if ($parent.find('.feedbackEntryItemErrorMessageRow').length === 0) $parent.find('.feedbackEntryItemLinks').prepend("<div class='feedbackEntryItemErrorMessageRow'></div>");
                    $parent.find('.feedbackEntryItemErrorMessageRow').append($parent.find('.feedbackEntryItemErrorMessage'));
                    $parent.find('.feedbackEntryItemErrorMessageRow').show();
                    $parent.find('.feedbackEntryItemErrorMessage').text(textAreaLengthError);

                    // Select text
                    input.prop('disabled', false);
                    input.select();

                    // Remove loading overlay
                    $overlay.remove();
                });
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/updatecomment', ajaxData);
    };
   
    // Events for initial checking when submitting a comment edit
    var editCommentOkLink_click = function () {
        if (readOnlyMode) {
            alert(readOnlyMessage);
            return false;
        }

        var $textArea = $(this).parents('.feedbackEntryItem').find('textarea');
        var inputText = $textArea.val();
        var trimmedText = $.trim(inputText);

        // If there is no text and no new images OR
        // if there is no text and deleted all images(no remaining images via delete during edit)
        if (trimmedText == '' && ((!$textArea.parents('.feedbackItem').find('.feedbackItem_thumbnail').length)
            || !$textArea.parents('.feedbackItem').find('.feedbackItem_thumbnail').is(':visible'))) {
            $textArea.val('');
            return false;
        }

        if ($.browser.msie && document.URL.toLowerCase().indexOf('http://') !== -1) {
            var encodedText = encodeURI(trimmedText);
            if (encodedText.length > 1600) {
                alert('Comment text is too long, please split into multiple comments.');
                return false;
            }
        }

        completeEditCommentOkLink_click($(this));

        return false;
    };

    // Events for when canceling editing a comment.
    var editCommentCancelLink_click = function () {
        var parent = $(this).parents('.feedbackEntryItem');

        // Remove Delete Icons and Unhide Thumbnails Prepared for Deletion
        parent.parent().find('.feedbackItem_thumbnail').each(function () {
            if(window.$axscreenshot !== undefined) {
                $axscreenshot.removeEditModeFromThumbnail($(this));
            }
        });

        parent.parents('.feedbackItem').find('.feedbackItem_commentText').show();
        parent.parents('.feedbackItem').find('.feedbackItem_commentLastModified').show();
        parent.parents('.feedbackItem').find('.feedbackItem_links').show();
        parent.parents('.feedbackItem').find('.feedbackItem_footer').show();
        parent.parents('.feedbackItem').find('.issueReplyInCommentsDiv').show();
        parent.remove();

        return false;
    };

    // Builds and binds the html for editing comments. Does not include appending the html!
    var getEditCommentControl = function (commentId, origCommentText) {
        // add the area for editing an entry
        var $editEntryQuery = $(getEditCommentHtml(commentId, origCommentText));

        // find the submit and cancel links and attach the click events
        $editEntryQuery.find('.feedbackEntryItemSave').click(editCommentOkLink_click);
        $editEntryQuery.find('.feedbackEntryItemCancel').click(editCommentCancelLink_click);

        // Bind text area error message
        bindTextAreaErrorMessage($editEntryQuery);

        return $editEntryQuery;
    };


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for deleting an issue
    /////////////////////////////////////////////////////////////////

    //Executes after a successful Delete Issue operation
    var finishDeleteIssue = function (tagVal, data) {
        // Deleting issues
        $('#issue_' + data.issue.code).remove();
        $('#pinId_' + data.issue.code).remove();

        if (managePageDeleteCallbackFunc) {
            managePageDeleteCallbackFunc();
            managePageDeleteCallbackFunc = null;
        }

        // Displays message to notify users that screenshot functionality is on another browser
        if(window.$axscreenshot !== undefined) {
            $axscreenshot.checkIfShowScreenshotBrowserMsg($('#feedbackSavedIssues'), !$('#rightPanel'));
        }

        // If there are no more exisiting issues, show the "No Discussion" message
        if ($('#feedbackSavedIssues .feedbackIssue').length === 0) {
            $('#feedbackNoDiscussionsDiv').show();
        }

        if(isManagePage) {
            $('#refreshTable').click();
        }
    };

    // Issue Delete Functionality
    var deleteIssue = function ($issueContainer, isPin) {
        if (readOnlyMode) {
            alert(readOnlyMessage);
            return false;
        }

        var issueId = isPin ? $issueContainer.parents('.savedPin').attr('id').replace('pinId_', '') : $issueContainer.attr('id').replace('issue_', '');
        var tagVal = $issueContainer.find(".issueDeleteConfirmDiv").attr("id").replace("feedbackTag_", "");
        var $pinContainer = isPin ? $issueContainer : $('#pinId_' + issueId);
        var $sideIssueContainer = isPin ? $('#issue_' + issueId) : $issueContainer;

        // Hide option popup and toggle reply button if it is on: Hide the reply feature
        $pinContainer.find('.toggleOn:visible, .hideRepliesButton:visible').add($sideIssueContainer.find('.toggleOn:visible, .hideRepliesButton:visible')).click();
        $pinContainer.find('.issueOptions').add($sideIssueContainer.find('.issueOptions')).hide();

        // Append overlay with a spinner (pin)
        $pinContainer.append('<div class="deleteItemOverlay"><img class="deleteItemSpinner"/></div>');

        var height_pin = $pinContainer.outerHeight();
        var $overlay_pin = $pinContainer.find('.deleteItemOverlay');
        $overlay_pin.height(height_pin);
        $overlay_pin.width($pinContainer.width());
        $overlay_pin.css('top', -1 * height_pin + 'px');
        $overlay_pin.css('margin-bottom', -1 * height_pin + 'px');

        if(isFirefox()) {
            $overlay_pin.css('margin-top', '6px');
        }

        // Append overlay with a spinner (sidepanel)
        $sideIssueContainer.append('<div class="deleteItemOverlay"><img class="deleteItemSpinner"/></div>');

        var height = $sideIssueContainer.outerHeight();
        var $overlay = $sideIssueContainer.find('.deleteItemOverlay');
        $overlay.height(height);
        $overlay.width($sideIssueContainer.outerWidth() + 6);
        $overlay.css('top', -1 * height + 'px');
        $overlay.css('left', '-6px');
        $overlay.css('margin-bottom', -1 * height + 'px');

        // Request data
        var ajaxData = {
            tag: tagVal,
            commenter: isUsingAxureAcct ? userName : getCommenterName(),
            userId: isUsingAxureAcct ? userId : '',
            issueCode: issueId,
            shortcut: $axure.player.settings.projectId,
            success: function (data) {
                getResponseOrFinish(data, tagVal, finishDeleteIssue);
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/delete', ajaxData);

        return false;
    }


    /////////////////////////////////////////////////////////////////
    /// Handles the HTML and Binding for deleting an comment
    /////////////////////////////////////////////////////////////////

    //Executes after a successful Delete Comment operation
    var finishDeleteComment = function (tagVal, data) {
        var $existingSideComment = $('#comment_' + data.comment.id);
        var $existingPinComment = $('#pinComment_' + data.comment.id);
        //var $feedbackIssue = $existingSideComment.parents('.feedbackIssue');
        //var $savedPin = $existingPinComment.parents('.savedPin');

        var issueId = $('#comment_' + data.comment.id + ', #pinComment_' + data.comment.id)
            .parents('.feedbackIssue, .savedPin').attr('id').replace('issue_', '').replace('pinId_', '').trim();
        var $feedbackIssue = $('#issue_' + issueId);
        var $savedPin = $('#pinId_' + issueId);
        var newCount = parseInt($feedbackIssue.find('.replyCount').text()) - 1;

        // Deleting comments
        $existingSideComment.remove();
        $existingPinComment.remove();

        // Update the Issue's State
        if (newCount < 1) {
            $feedbackIssue.find('.hideRepliesButton').click();
            $feedbackIssue.find('.showRepliesButton').hide();
            $feedbackIssue.find('.issueReplyInCommentsDiv').remove();
            $feedbackIssue.find('.replyToggleButton').show();
            $feedbackIssue.find('.replyToggleButton.toggleOn').click();

            $savedPin.find('.hideRepliesButton').click();
            $savedPin.find('.showRepliesButton').hide();
            $savedPin.find('.issueReplyInCommentsDiv').remove();
            $savedPin.find('.replyToggleButton').show();
            $savedPin.find('.replyToggleButton.toggleOn').click();
        } else {
            // Update the Show Reply count - For when value changes
            $feedbackIssue.find(".replyCount").text(newCount).change();
            $savedPin.find(".replyCount").text(newCount).change();
        }
    };

    // TODO: Fix - Spinner location
    // Delete Comment Functionality - Preparation
    var deleteComment = function ($commentContainer, isPin) {
        if (readOnlyMode) {
            alert(readOnlyMessage);
            return false;
        }

        var commentId = isPin ? $commentContainer.attr('id').replace('pinComment_', '') : $commentContainer.attr("id").replace("comment_", "");
        var tagVal = $commentContainer.find(".commentDeleteConfirmDiv").attr("id").replace("feedbackTag_", "");
        var $pinComment = isPin ? $commentContainer : $('#pinComment_' + commentId);
        var $sideIssueComment = isPin ? $('#comment_' + commentId) : $commentContainer;

        // Hides comment option popup
        $pinComment.find('.commentOptions').add($sideIssueComment.find('.commentOptions')).hide();

        // Append overlay with a spinner (pin)
        $pinComment.append('<div class="deleteItemOverlay"><img class="deleteItemSpinner"/></div>');

        var pinHeight = $pinComment.outerHeight();
        var $pinOverlay = $pinComment.find('.deleteItemOverlay');
        $pinOverlay.height(pinHeight);
        $pinOverlay.width($pinComment.width());
        $pinOverlay.css('top', -1 * pinHeight + 'px');
        $pinOverlay.css('margin-bottom', -1 * pinHeight + 'px');

        // Append overlay with a spinner (sidepanel)
        $sideIssueComment.append('<div class="deleteItemOverlay"><img class="deleteItemSpinner"/></div>');

        var height = $sideIssueComment.outerHeight();
        var $overlay = $sideIssueComment.find('.deleteItemOverlay');
        $overlay.height(height);
        $overlay.width($sideIssueComment.outerWidth());
        $overlay.css('top', -1 * height + 'px');
        $overlay.css('margin-bottom', -1 * height + 'px');

        var ajaxData = {
            tag: tagVal,
            commenter: isUsingAxureAcct ? userName : getCommenterName(),
            userId: isUsingAxureAcct ? userId : '',
            commentId: commentId,
            shortcut: $axure.player.settings.projectId,
            success: function (data) {
                getResponseOrFinish(data, tagVal, finishDeleteComment);
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/deletecomment', ajaxData);

        return false;
    }


    /////////////////////////////////////////////////////////////////
    /// Pinning
    /////////////////////////////////////////////////////////////////

    // From a pin color selector, sets the default color for the given session
    function updatePinColor($selectedColor, $container) {
        $('.selectedPinColorOption').removeClass('selectedPinColorOption');

        // Get class of selected option
        var classes = $selectedColor.attr('class').split(' ');
        var originalColorClass = $axpin.getOrSetCurrentPinColorClass();
        var color = '';

        for (var i = 0; i < classes.length; ++i) {
            if (classes[i].indexOf('pinColor_') > -1) {
                color = classes[i];
                break;
            }
        }

        // Update default pin color
        var colorIndex = color.replace('pinColor_', '');
        $axpin.getOrSetCurrentPinColorClass(colorIndex);
        $container.parents('.pin').add($container.find('.feedbackUserPin')).removeClass(originalColorClass).addClass($axpin.getOrSetCurrentPinColorClass());
        $selectedColor.addClass('selectedPinColorOption');
    };


    // Prepares and send server request to perform new pin addition.
    function completeNewPinCommentCreate($container, issueCode) {
        //if ($('#feedbackSavedIssues .preloadDiv').length) { return; }

        //var $parent = $thisObj.parents('.feedbackEntryItem');
        //var issueCode = $parent.attr('issueCode');

        // we want to re-enable the reply link
        //$parent.parent().find('.editIssueLinks').show();

        var input = $container.find('textarea');

        // Send GUIDS not the data url!
        var submittedImgs = [];
        $container.find('.feedbackItem_thumbnail').each(function () {
            if ($(this).is(':visible')) {
                var idTag = $(this).attr('id');
                submittedImgs.push(idTag.substring(idTag.indexOf('_') + 1));
            }
        });

        // Add an overlay over the the text area and show the spinner 
        var text = $.trim(input.val());
        $container.find('textarea').attr('disabled', 'disabled');
        $container.append('<div class="addingCommentOverlay"><img class="addingCommentSpinner"/></div>');

        var $overlay = $container.find('.addingCommentOverlay');
        var $textAreaContainer = $container.find('.feedbackEntryTextAreaContainer');
        var $links = $container.find('.feedbackEntryItemLinks');

        // Adding overlay either to the entire container or just the text box.
        if (submittedImgs.length > 0) {
            $overlay.css('margin-top', -1 * $container.outerHeight() + 'px');
            $overlay.height($container.outerHeight());
            $overlay.width($container.outerWidth());
        } else {
            var top = $links.outerHeight();
            $overlay.css('top', -1 * top + 'px');
            $overlay.css('margin-top', -1 * ($textAreaContainer.height()) + 'px');
            $overlay.height($textAreaContainer.height());
            $overlay.width($textAreaContainer.outerWidth());
        }

        // Disable buttons (links)
        $container.find('.feedbackEntryItemLinks a').each(function () {
            $(this).css('pointer-events', 'none');
        });

        // Remove all screenshot tools
        $axscreenshot.removeAll();

        var tagVal = $container.attr('id').replace('newPinComment_', '');

        var ajaxData = {
            tag: tagVal,
            issueCode: issueCode,
            pageId: $axure.page.id,
            userId: isUsingAxureAcct ? userId : '',
            commenter: isUsingAxureAcct ? userName : getCommenterName(),
            commentText: text,
            shortcut: $axure.player.settings.projectId,
            images: JSON.stringify(submittedImgs),
            success: function (data) {
                getResponseOrFinish(data, tagVal, finishAddOrUpdateComment, function() {
                    // Error message
                    if ($container.find('.feedbackEntryItemErrorMessageRow').length === 0) $container.find('.feedbackEntryItemLinks').prepend("<div class='feedbackEntryItemErrorMessageRow'></div>");
                    $container.find('.feedbackEntryItemErrorMessageRow').append($container.find('.feedbackEntryItemErrorMessage'));
                    $container.find('.feedbackEntryItemErrorMessageRow').show();
                    $container.find('.feedbackEntryItemErrorMessage').text(textAreaLengthError);

                    // Select text
                    input.prop('disabled', false);
                    input.select();

                    // Remove loading overlay
                    $overlay.remove();
                });
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/addcomment', ajaxData);
    };

    // Bind the html for an existing pin
    function existingPinNewCommentEventBinding($newPinCommentContainer) {
        loadFeedbackUserInfo($newPinCommentContainer.find('.feedbackUserHeaderTable'));
        bindAnonNameChangeEvents($newPinCommentContainer, false);

        // Bind watermarking for textarea
        $newPinCommentContainer.find('.feedbackEntryTextArea').addClass('watermark').val(replyReminderString).focus(function () {
            if ($(this).val() === replyReminderString) {
                $(this).removeClass("watermark errorWatermark").val("");
            }
        }).blur(function () {
            if ($(this).val() === "") {
                $(this).addClass("watermark").val(replyReminderString);
            }
        });

        // Focus and scroll to the bottom
        $newPinCommentContainer.find('textarea').focus();
        $newPinCommentContainer.parents('.pinIssueScrollContainer').scrollTop($newPinCommentContainer.parents('.pinIssueContainer').height());

        // Login link click
        $newPinCommentContainer.find('.loginOption').click(function () {
            var $loginContainer = $newPinCommentContainer.parents('.pinIssueBubble').find('.axureLoginContainer');
            $loginContainer.find('.errorMessage').text('');
            $loginContainer.find('.errorMessage').hide();

            bindAnonNameChangeEvents($newPinCommentContainer.parents('.pinIssueBubble'), true);
            bindAxureLoginContainerEvent($loginContainer, false, true);

            $newPinCommentContainer.parents('.pinIssueContainer, .pinReplyContainer').hide();
            $loginContainer.css('display', 'block');
        });

        // Logout link click
        $newPinCommentContainer.find('.logoutOption').click(function () {
            function completeLogout() {
                isUsingAxureAcct = false;

                loadFeedbackUserInfo($newPinCommentContainer.parents('.pinIssueBubble').find('.newPinCommentContainer .feedbackUserHeaderTable'));
                loadAnonNameChangeInfo($newPinCommentContainer.parents('.pinIssueBubble').find('.newPinCommentContainer'));
                bindAnonNameChangeEvents($newPinCommentContainer.parents('.pinIssueBubble'), false);
                showOrHideMenuOptions();
                recallPreviousPinColor();

                userId = '';
                userProfileImg = '';
            };

            if (window.$axshare != null) {
                $axshare.logout(completeLogout);
            } else {
                completeLogout();
            }
        });

        // Pin Comment Submit
        $newPinCommentContainer.find('.newPinCommentPostBtn').click(function () {
            if (/*$('#feedbackSavedIssues .preloadDiv').length ||*/ $('.pinTargetOverlay').length) { return false; }

            if (readOnlyMode) {
                alert(readOnlyMessage);
                return false;
            }

            // Verify that there is text OR screenshots taken
            var $container = $(this).parents('.newPinCommentContainer');
            var $input = $container.find('textarea');
            var inputText = $input.val();
            var trimmedText = $.trim(inputText);

            // TODO: Verify img class names
            var hasImages = $container.find('.feedbackItem_thumbnailImg').length > 0;
            if ((trimmedText === "" || inputText === replyReminderString) && !hasImages) {
                $input.val(replyReminderString);
                //$input.select();
                $input.addClass('errorWatermark');

                return false;
            }

            // Perform actual submission and pin storage
            var issueCode = $(this).parents('.savedPin').attr('id').replace('pinId_', '');
            completeNewPinCommentCreate($container, issueCode);

            return false;
        });

        // Bind text area error message
        bindTextAreaErrorMessage($newPinCommentContainer);
    };

    // TODO: Remove sign up link for onprem
    // Generates the pin bubble for an existing pin issue along with the container for saved comments and 
    // container for making a new comment
    function existingPinHtml(issue, profiles, isResolved, tailPosition, isShifted) {
        var currentDate = new Date();
        var profile = profiles != null ? profiles[issue.userId] : null;
        var hasProfile = profile != null;
        var hasProfileImage = profile != null && profile.profileImg != null;
        var positioning = $axpin.getPinBubblePositioning(tailPosition, isShifted);
        var result = [];

        result.push(
            "<div class='pin savedPin ", positioning.pinClass, ' ', $axpin.getPinColorClassFromIndex(issue.color), isResolved ? " resolvedPin" : "", "' id='pinId_", issue.code, "' style='top: ", issue.y, "px; left: ", issue.x, "px;'>",
                (parseInt(issue.pinNumber) > 99 ? '+' : issue.pinNumber),
            "   <div class='pinIssueBubble axPinContextSpeechBubble' style='top: ", positioning.bubbleTop, "px; left: ", positioning.bubbleLeft, "px;'>",
            "       <div class='pinIssueScrollContainer'>",
            "           <div class='pinIssueContainer'>",
            "               <table class='feedbackUserHeaderTable'><tbody><tr>",
            "                   <td class='commenterImgTd'>",
            '                       <div class="commenterImg ', (!hasProfile || !hasProfileImage  ? 'anonCommenterImg' : ''), '" style="', (!hasProfileImage ? '' : ('background-image: url(\'' + profile.profileImg + '\')')), '">',
                                        (!hasProfile || !hasProfileImage ? issue.creator.charAt(0).toUpperCase() : ''),
            "                       <div class='feedbackUserPin ", $axpin.getPinColorClassFromIndex(issue.color), "'>", issue.pinNumber, "</div>",       // For pin number
            "                   </td>",
            "                   <td class='feedbackUserNameTd'>",
            "                       <div class='feedbackUserName' title='", issue.creator, "'>", issue.creator, "</div>",
            "                   </td>",
            "                   <td class='relativeCreatedDateTd'>",
            "                       <div class='relativeCreatedDate'>", buildRelativeDate(currentDate, new Date(Date.parse(issue.createdDate))), "</div>",
            "                   </td>",
            "               </tr></tbody></table>",
            "               <div class='pinTopicDescription' userid='", issue.userId, "'>", issue.description.replace('\n', '<br>'), "</div>"
        );
            
        // Screenshots
        if (issue.screenshotIds != null && issue.screenshotIds.length > 0) {
            for (var i = 0; i < issue.screenshotIds.length; ++i) {
                result.push(
                    '   <div class="feedbackItem_thumbnail" id="screenshot_', issue.screenshotIds[i], '">',
                    '       <div class="feedbackItem_thumbnailImg">',
                    '           <img class="thumbnailImg" src="', issue.screenshotUrls[i], '" />',
                    '           <img class="fullsizeImg" src="', issue.fullsizeUrls[i], '" />',
                    '       </div>',
                    '       <div class="preloadDiv">',
                    '       </div>',
                    '   </div>');
            }
        }

        result.push(
            "               <div class='topicFooter'>",
            "                   <div class='topicOptions'>•••</div>",
            "                   <div class='showRepliesButton' style='", issue.numComments > 0 ? "" : "display: none;", "'>",
            "                       <span class='replyCount'>",
            issue.numComments,
            "                       </span>",
            "                       <span class='showReplyText'> ", issue.numComments === 1 ? "Reply" : "Replies", " ▶</span>",
            "                   </div>",
            "                   <div class='hideRepliesButton'>",
            "<span>Hide Replies ▼</span>",
            "                   </div>",
            "                   <a class='replyToggleButton toggleOff' style='", issue.numComments > 0 ? "display: none;" : "", "'>Reply</a>",
            "               </div>",

            "               <div class='pinIssueComments'></div>",
            "       </div>",
            "   </div>",
            "   <div class='pinReplyContainer'>",
            "   </div>",
            "   <div class='axureLoginContainer'>",
            "       <span style='font-weight: bold; font-size: 10px;'>Log in to your Axure Share account</span>",
            "       <input type='text' name='email' class='axureEmail' style='margin-top: 7px;'/>",
            "       <input name='password' class='axurePassword' />",
            "       <div class='feedbackGreenBtn'>LOG IN</div>",
            "       <div class='errorMessage'></div>",
            "       <div style='text-align: right; margin-top: 5px; font-size: 10px;'>",
            "           <a class='axureCancelLink' style='float: left;'>Cancel</a>",
                        buildSignUpLinkDivHtml(),
            "       </div>",
            "   </div>",
            "</div>"
        );

        return result.join("");
    };

    // Binds pin issue bubble events. Includes login/logout, anon name change, toggle pin, and new comment submission
    function existingPinEventBinding($pin) {
        // Init
        $pin.find('.pinIssueBubble').hide();
        $pin.find('.pinIssueScrollContainer').isolatedScroll();
        $('.savedPin').css('z-index', '0');

        // Pin toggle to show/hide bubble
        $pin.click(function (e) {
            //if dragged
            if($pin == $axpin.suppressClickPin) {
                $axpin.suppressClickPin = null;
                return;
            }

            // Ignore children elements
            if (e.target !== $pin[0]) {
                return;
            }

            if (!$(this).find('.pinIssueBubble').is(':visible')) {
                $('.expandedPin').removeClass('expandedPin');
                $('.pinIssueBubble').hide();

                $pin.addClass('expandedPin');
                $(this).find('.pinIssueBubble').show();
                
                loadFeedbackUserInfo($pin.find('.newPinCommentContainer .feedbackUserHeaderTable'));

                // Set current one on top of the other pins
                $(this).css('z-index', ++$axpin.pinZindex);
            } else {
                $(this).find('.pinIssueBubble').hide();
                $('.expandedPin').removeClass('expandedPin');
            }
        });

        // Manages setup for moving the pin
        bindPinToBeMoved($pin, false);
        
        // Reply toggle
        bindViewCommentsAndReplyButtons($pin.find('.pinIssueContainer'), true);

        // Options Menu
        bindTopicOptionsPopupMenu($pin.find('.pinIssueContainer'), true);
    };


    // Very similar to completeFeedbackNewIssueCreate()
    function completeNewPinIssueCreate($thisObj, x, y, color, number) {
        var $parent = $thisObj.parents('.newPinIssueContainer');
        var $scrollContainer = $parent.parent();
        var $input = $parent.find('textarea');

        //if ($parent.find('a').prop('disabled')) { return false; }
        //$parent.find('.newPinIssuePostBtn').addClass('newPinPostSpinnerBtn');

        // Send GUIDS not the data url! - Images to be notified to be saved.
        var submittedImgs = [];
        $thisObj.parents('.newPinIssueBubble').find('.feedbackItem_thumbnail').each(function () {
            if ($(this).is(':visible')) {
                var idTag = $(this).attr('id');
                submittedImgs.push(idTag.substring(idTag.indexOf('_') + 1));
            }
        });

        $parent.find('textarea').attr('disabled', 'disabled');
        $parent.append('<div class="addingCommentOverlay"><img class="addingCommentSpinner"/></div>');

        var $overlay = $parent.find('.addingCommentOverlay');
        var $textAreaContainer = $parent.find('.feedbackEntryTextAreaContainer');
        var $links = $parent.find('.feedbackEntryItemLinks');

        // Adding overlay either to the entire container or just the text box.
        if (submittedImgs.length > 0) {
            $overlay.insertAfter($scrollContainer.last('.feedbackItem_thumbnail'));
            $overlay.css('margin-top', -1 * $scrollContainer.height() + 'px');
            $overlay.height($scrollContainer.height());
            $overlay.width($parent.outerWidth());
        } else {
            var top = $links.outerHeight();
            $overlay.css('top', -1 * top + 'px');
            $overlay.css('margin-top', -1 * ($textAreaContainer.height()) + 'px');
            $overlay.height($textAreaContainer.outerHeight());
            $overlay.width($textAreaContainer.outerWidth());
        }

        // Disable input, camera, and submit button
        //disableNewIssueEntry(parent, true);

        // Update Issue Container and build temporary processing issue.
        var tagVal = $parent.attr('id').replace('newPin_', '');
        var text = $.trim($input.val());
        prepareNewIssueEntry($parent, false, true);

        // Remove all screenshot and preview panels
        $axscreenshot.removeAll();

        // TODO: Need to determine whether to round up or floor
        // Pin position info
        var scroll = $axpin.determineIframeScroll();
        var position = $axpin.determineIframePosition();
        //var borderOffset = 2 * parseInt($('.pinBorderOverlay').css('border-width'));
        var borderOffset = 2 * parseInt($('.pinBorderOverlay').css('border-top-width'));
        var finalX = x + scroll.scrollLeft - position.left - borderOffset;
        var finalY = y + scroll.scrollTop - position.top - borderOffset;

        // Submit Ajax request to save issue and move to different section.
        var ajaxData = {
            tag: tagVal,
            shortcut: $axure.player.settings.projectId,
            pageId: $axure.page.id,
            pageName: $axure.page.pageName,
            userId: isUsingAxureAcct ? userId : '',
            creator: isUsingAxureAcct ? userName : getCommenterName(),
            description: text,
            images: JSON.stringify(submittedImgs),
            x: Math.round(finalX),
            y: Math.round(finalY),      
            color: color,
            pinNumber: number,
            success: function (data) {
                getResponseOrFinish(data, tagVal, finishAddPinIssue, function() {
                    // Error message
                    if ($parent.find('.feedbackEntryItemErrorMessageRow').length === 0) $parent.find('.feedbackEntryItemLinks').prepend("<div class='feedbackEntryItemErrorMessageRow'></div>");
                    $parent.find('.feedbackEntryItemErrorMessageRow').append($parent.find('.feedbackEntryItemErrorMessage'));
                    $parent.find('.feedbackEntryItemErrorMessageRow').show();
                    $parent.find('.feedbackEntryItemErrorMessage').text(textAreaLengthError);

                    // Select text
                    $input.prop('disabled', false);
                    $input.select();

                    // Remove loading overlay
                    $overlay.remove();
                });
            }
        };
        includeTokens(ajaxData);

        sendAjax(feedbackServiceUrl + '/add', ajaxData);

        //return false;
    };

    // Adjusts the width position of the header in the new pin issue bubble. Also prepares the anon name text if necessary
    function newPinBubbleHeaderAdjustment($container, isAnonVisible) {
        if (isAnonVisible) {
            $container.find('.feedbackUserNameTd').attr('colspan', '2');
            $container.find('.feedbackUserNameTd').css('width', 'initial');

            var $anonNameSpan = $container.find('.anonNameChangeDisplay');
            var $anonNameInput = $container.find('.anonNameChangeInput');
            var currName = getCommenterName();

            $anonNameSpan.text(currName);
            $anonNameInput.val(currName);

        } else {
            $container.find('.feedbackUserNameTd').attr('colspan', '1');
            $container.find('.feedbackUserNameTd').css('width', '124px');
        }
    };

    // Loads and binds the relative info and events for when a new pin bubble is made
    function newPinBubbleEventBinding($container, x, y, color, number) {
        // Container info loading and adjustment
        loadFeedbackUserInfo($container.find('.feedbackUserHeaderTable'));
        bindAnonNameChangeEvents($container.parent(), false);
        newPinBubbleHeaderAdjustment($container, $container.find('.anonFeedbackUserName').is(':visible'));

        // Init
        $container.find('.pinColorSelectorTd .' + $axpin.getOrSetCurrentPinColorClass()).addClass('selectedPinColorOption');
        $container.find('textarea').focus();

        // Color selection click
        $container.find('.pinColorOption').click(function () {
            updatePinColor($(this), $container);
        });

        // Login link click
        $container.find('.loginOption').click(function () {
            var $loginContainer = $container.parents('.pin').find('.axureLoginContainer');
            $loginContainer.find('.errorMessage').text('');
            $loginContainer.find('.errorMessage').hide();

            bindAnonNameChangeEvents($container.parent(), true);
            bindAxureLoginContainerEvent($loginContainer, true, false, newPinBubbleHeaderAdjustment);

            //$container.hide();
            $container.parent().hide();
            $loginContainer.show();
        });

        // Logout link click
        $container.find('.logoutOption').click(function () {
            function completeLogout() {
                isUsingAxureAcct = false;

                loadFeedbackUserInfo($container.find('.feedbackUserHeaderTable'));
                loadAnonNameChangeInfo($container);
                bindAnonNameChangeEvents($container.parent(), false);
                newPinBubbleHeaderAdjustment($container, true);
                showOrHideMenuOptions();
                recallPreviousPinColor();

                userId = '';
                userProfileImg = '';
            };

            if (window.$axshare != null) {
                $axshare.logout(completeLogout);
            } else {
                completeLogout();
            }
        });

        // Manages setup for moving the pin
        bindPinToBeMoved($container.parents('.pin'), true);

        // Bind text area error message
        bindTextAreaErrorMessage($container);

        // New Pin Issue Submit
        $container.find('.newPinIssuePostBtn').click(function () {
            if ($('#feedbackSavedIssues .preloadDiv').length || !$('.pinTargetOverlay').length) { return false; }

            if (readOnlyMode) {
                alert(readOnlyMessage);
                return false;
            }

            // Verify that there is text OR screenshots taken
            var $input = $(this).parents('.newPinIssueContainer').find('textarea');
            var inputText = $.trim($input.val());

            // If no text, update placeholder text, color and return
            if (inputText === '') {
                $input.attr('placeholder', 'Please enter text here');
                $input.addClass('errorTextArea');
                $input.focus();

                return false;
            }

            //var hasImages = $(this).parents('.newPinIssueBubble').find('.feedbackItem_thumbnailImg').length > 0;
            //if ((inputText === "" || inputText === reminderString || inputText === pinReminderString) && !hasImages) {
            //    $input.val(reminderString);
            //    $input.select();

            //    return false;
            //}

            // Ensure that the most up to date location is saved
            var $pin = $container.parents('.pin');
            var halfPinWidth = $pin.width() / 2;
            //var addPinPanelBorderWidth = parseInt($('.pinBorderOverlay').css('border-width'));
            var addPinPanelBorderWidth = parseInt($('.pinBorderOverlay').css('border-top-width'));
            var newX = parseInt($pin.css('left')) + halfPinWidth + addPinPanelBorderWidth;
            var newY = parseInt($pin.css('top')) + halfPinWidth + addPinPanelBorderWidth;

            // Perform actual submission and pin storage
            completeNewPinIssueCreate($(this), newX, newY, $axpin.getCurrentPinColorIndex(), number);

            return false;
        });
    };

    // Tells axPin to prepare the add panel pin and bind what actions to take when click is performed
    function feedbackNewIssuePinCreateEvent() {
        if ($('#feedbackSavedIssues .preloadDiv').length) { return false; }

        if (readOnlyMode) {
            alert(readOnlyMessage);
            return false;
        }

        // Prepare the page to add pins
        $axpin.createAddPinPanel($('#feedbackNewIssuePinBtn'), newPinBubbleHtml, newPinBubbleEventBinding);

        return false;
    };


    //---------------------------------------------------------------------------------------------------------------------------
    //----------END OF EVENT BINDING---------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------


    //---------------------------------------------------------------------------------------------------------------------------
    //----------FEEDBACK PLUGIN LOADING------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------

    // Performs updates when poll request returns
    function pollUpdate(ajaxResponse) {
        if(ajaxResponse.continuePolling === false) {
            isPollingEnabled = false;
            return;
        }

        if (!ajaxResponse.success && ajaxResponse.redirecturl) {
            window.location.href = ajaxResponse.redirecturl;
            return;
        }

        // If for any reason the poll times do not match (aka manual load happened), then skip this poll update
        if(lastPollTime !== ajaxResponse.originalPollTime) {
            return;
        }

        // Update last time client performed server pull
        updateLastPollTime(ajaxResponse.lastPollTime);

        // If polling failed or that there was no updates since the last poll, return
        if(!ajaxResponse.success || !ajaxResponse.hasChanges) {
            return;
        }

        // Show little notification notifying changes. 
        if(!$('#refreshNotification').is(':visible')) {
            $('#refreshNotification').css('width', $('#feedbackHost').width());
            $('#refreshNotification').slideToggle(500);
        }
    };

    // Defines the ajax call to be looping as well as calling the func to be run on every ajax response
    function initPollLoop() {
        (function pollingLoop() {
            var pollLength = 60000;

            // Stop polling if necessary
            if(!isPollingEnabled) {
                return;
            }

            // Retry later when the sidebar is available or if the notification is already present
            if(!$('#feedbackHost').is(':visible') || $('#refreshTicker').is(':visible')) {
                setTimeout(pollingLoop, pollLength);
                return;
            }

            // If we dont have the basic info, retry in 10 seconds
            if ($axure.player.settings.projectId == null || $axure.page.id == null) {
                setTimeout(pollingLoop, 10000);
                return;
            }

            var ajaxData = {
                shortcut: $axure.player.settings.projectId,
                pageId: $axure.page.id,
                lastPollTime: lastPollTime,
                creator: isUsingAxureAcct ? userName : getCommenterName(),
                userId: isUsingAxureAcct ? userId : ""
            };
            includeTokens(ajaxData, true);

            $.ajax({
                type: 'GET',
                url: feedbackServiceUrl + '/pollforupdates',
                data: ajaxData,
                success: function (response) {
                    pollUpdate(response);
                    setTimeout(pollingLoop, pollLength);
                },
                dataType: 'jsonp'
            });
        }());
    };

    // Initializes the screenshot and pinning tool
    function loadFeedbackTools() {
        // Init axscreenshot tool and axpin tool
        if(window.$axscreenshot != null) {
            $axscreenshot.init($('#leftPanel'), $('#rightPanel'), $('#outerContainer'), $('#interfaceControlFrameHeader, #interfaceControlFrameMinimizeContainer'),
                ['feedbackEntryItem', 'pinFeedbackEntryItem'],
                ['feedbackNewIssue', 'newPinIssueScrollContainer', 'feedbackIssue', 'pinIssueContainer'],
                ['feedbackTag_', 'newPinComment_']);
        }

        if(window.$axpin != null) {
            $axpin.init($('#leftPanel'), $('#rightPanel'), $('#outerContainer'), $('#interfaceControlFrameHeader, #interfaceControlFrameMinimizeContainer'));
        }

        // Init polling ajax update for issues
        if(isPollingEnabled) {
            initPollLoop();
        }
    }

    // See if all relavant libraries have been loaded
    function areToolsAllLoaded() {
        var result = window.$axshare == null ||
            $axshare.auth == null ||
            $axshare.login == null ||
            window.$axscreenshot == null ||
            window.$axpin == null ||
            window.addResizeListener == null;
           
        if (!isLocalChrome()) {
            var iframe = $('#rightPanel').find('iframe')[0];
            var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

            result = result || iframeDoc.readyState !== 'complete';
        }

        return !result;
    };

    // This loads the entries (issues) for the current page in the plugin.
    // The callback of this function is the listCompleted function which processes the result
    var clearAndLoadEntries = function (isRefresh) {
        var savedIssuesDiv = $('#feedbackSavedIssues');
        var resolvedIssuesDiv = $("#feedbackResolvedIssuesDiv");

        if (!isRefresh) {
            var pageName = '';
            if ($axure.page.pageName) pageName = $axure.page.pageName;
            $('#feedbackPageNameHeader').html(pageName);
            $('#feedbackPageNameHeader').attr('title', 'Discuss ' + pageName);

            $('#feedbackNewIssue').html('').append(getNewIssueControl());
            $('#feedbackNewIssuePin').html('').append(getNewIssuePinControl());

            // New Topic focus event
            $('.issueFeedbackEntryTextArea').focus(function () {
                $axscreenshot.setKeyboardListener($(this));
                //$currFeedbackEntry_keyboard = $(this);
            });

            savedIssuesDiv.removeClass('loading');
        }

        if (savedIssuesDiv.is('.loading')) {
            return;
        } else {
            savedIssuesDiv.addClass('loading');
        }

        var ajaxData = {
            shortcut: $axure.player.settings.projectId,
            pageId: $axure.page.id
        };
        includeTokens(ajaxData, true);

        savedIssuesDiv.html('');
        resolvedIssuesDiv.find('.feedbackIssue').remove();
        $.ajax({
            type: 'GET',
            url: feedbackServiceUrl + '/list',
            data: ajaxData,
            success: function (response) {
                if (!response.success && response.redirecturl) {
                    window.location.href = response.redirecturl;
                    return;
                }
                // Update the last time the client updated against the server
                updateLastPollTime(response.data.lastPollTime);

                var sortedIssues = fixIssueDatesAndSort(response.data.issues);
                var unresolvedIssues = [];
                var resolvedIssues = [];

                $.each(sortedIssues, function(index, value) {
                    if(value.resolvedDate != null) {
                        resolvedIssues.push(value);
                    } else {
                        unresolvedIssues.push(value);
                    }
                });

                var unresolvedIssueObjs = getIssuesControl(unresolvedIssues, response.data.profiles, false);
                var resolvedIssueObjs = getIssuesControl(resolvedIssues, response.data.profiles, false, true);
                var issueQuery = $.merge(unresolvedIssueObjs, resolvedIssueObjs);
                savedIssuesDiv.append(unresolvedIssueObjs);
                resolvedIssuesDiv.append(resolvedIssueObjs);

                // Show/Hide Resolved Issues Btn
                if(resolvedIssues.length !== 0) {
                    $('#showResolvedIssuesBtn').show();
                } else {
                    $('#showResolvedIssuesBtn').hide();
                }

                // Show/Hide "No Discussions" Div
                if(unresolvedIssues.length + resolvedIssues.length === 0) {
                    $('#feedbackNoDiscussionsDiv').show();
                } else {
                    $('#feedbackNoDiscussionsDiv').hide();
                }

                // Screwy IE 8 Bug: Sometimes for IE8, items aren't registered as loaded yet so it freaks out.
                if ($axscreenshot.isIE8) {
                    (function wait() {
                        if (window.loaded) {
                            // Displays message to notify users that screenshot functionality is on another browser
                            $axscreenshot.checkIfShowScreenshotBrowserMsg($('#feedbackSavedIssues'), !$('#rightPanel'));

                            // Screenshot Preloading Complete Callbacks
                            issueQuery.find('.thumbnailImg').each(function () {
                                $(this).load(function () {
                                    $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                                    $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                                });
                            });
                        } else {
                            setTimeout(wait, 5);
                        }
                    })();
                } else {
                    // Screenshot Preloading Complete Callbacks
                    issueQuery.find('.thumbnailImg').each(function () {
                        $(this).load(function () {
                            $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                            $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                        });
                    });
                }

                savedIssuesDiv.removeClass('loading');
                updateContainerHeight();
                recallPreviousPinColor();
                recallNewPinNumber();

                // If feedback tab is not currently selected, hide pins
                if(!$('#feedbackHostBtn a').hasClass('selected') && !$('#feedbackHost').is(':visible')) {
                    //$axpin.showPins(false, true);
                }

                // On complete loading of issues, hide the spins if the fragment variable is present
                if (getFragmentVariable("dp") === "0") {
                    $('#feedbackButtonShowHidePins').removeClass('pin_on').addClass('pin_off');
                    if(window.$axpin != null) {
                        $axpin.showPins(false, true);
                    }
                }
            },
            dataType: 'jsonp'
        });
    };

    // This loads the comments for the given issue.
    var loadFeedbackForIssue = function (commentsDivQuery, issuecode, jumpToAddBox, isPin, success) {
        if (commentsDivQuery.is('.loading')) {
            return;
        } else {
            commentsDivQuery.addClass('loading');
        }

        commentsDivQuery.empty();
        commentsDivQuery.append('<img src="' + contentUrlPrefix + '/Content/plugins/feedback/styles/images/spinner.gif" style="width: 15px" alt="Loading...">');

        // Is unresolved if viewing with the side panel or via the issues manage page on share
        //var isUnresolved = ($('#feedbackSavedIssues').is(':visible') && $('#feedbackSavedIssues').find('#issue_' + issuecode).length !== 0) || ($('#issueDisplay').length === 1);
        var ajaxData = {
            issueCode: issuecode,
            shortcut: $axure.player.settings.projectId
        };
        includeTokens(ajaxData, true);

        $.ajax({
            type: 'GET',
            url: feedbackServiceUrl + '/getcommentsbyissuecode',
            data: ajaxData,
            success: function (response) {
                if (!response.success && response.redirecturl) {
                    window.location.href = response.redirecturl;
                    return;
                }

                var comments = fixCommentDatesAndSort(response.data.comments);
                var commentQuery = getCommentsControl(comments, response.data.profiles, response.data.isUnresolved, isPin, !response.data.isUnresolved);

                if(comments.length === 0) {
                    commentQuery = $('<div class="noCommentsMessage">no comments</div>');
                }

                //This prevents the comments from being appended to the div if the
                //user quickly hit the "collapse" button after hitting expand (therefore expand visible again)
                if(commentsDivQuery.is(":visible")) {
                    commentsDivQuery.empty();
                    commentsDivQuery.append(commentQuery);

                    // TODO: Update - Just call binding process
                    bindCommentsControlPostAppend($(commentQuery), isPin);
                    //commentsDivQuery.append(getEditIssueLinksControl());
                    //getEditIssueLinksControl();

                    commentsDivQuery.find('.thumbnailImg').each(function() {
                        $(this).load(function() {
                            $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                            $axscreenshot.screenshotBindToDiv($(this).parent().parent());
                        });
                    });

                    if(jumpToAddBox) {
                        commentsDivQuery.find('a.feedbackIssueNewCommentLink').click();
                    }

                    //If this issue is being displayed within a dialog (outside of a prototype), then update
                    //the dialog to recenter it
                    if(commentsDivQuery.parents('#issueDisplay').length > 0) {
                        commentsDivQuery.parents('#issueDisplay').axDialog('update');
                    }
                }

                commentsDivQuery.removeClass('loading');
                showOrHideMenuOptions();
                recallPreviousPinColor();
            },
            dataType: 'jsonp'
        });
    };

    // Initialize Feedback Plugin
    var loadPlugin = function () {
        // add the plugin to the interface plugin area
        $axure.player.createPluginHost({
            id: 'feedbackHost',
            context: 'interface',
            title: 'Discuss',
            gid: 4
        });

        // load when feedback loads - note, this gets called first thing
        $axure.page.bind('load.feedback', function (e) {
            reloadPins();

            (function iframeLoaded() {
                if(isLocalChrome()) {
                    $('#feedbackResolvedIssuesDiv').hide();
                    clearAndLoadEntries(false);
                    return;
                }

                var iframe = $('#rightPanel').find('iframe')[0];
                var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

                if (iframeDoc.readyState == 'complete' && areToolsAllLoaded()) {
                    $('#feedbackResolvedIssuesDiv').hide();
                    clearAndLoadEntries(false);
                } else {
                    setTimeout(iframeLoaded, 100);
                }
            })();
        });

        // append base containers
        $('#feedbackHost').append('<div id="feedbackHeader" class="feedbackHeader"></div>');
        $('#feedbackHost').append('<div id="feedbackNewIssueContainer"></div>');
        $('#feedbackHost').append('<div id="feedbackLoadingPlaceholder"></div>');
        $('#feedbackHost').append('<div id="feedbackNoDiscussionsDiv"></div>');
        $('#feedbackHost').append('<div id="feedbackScrollContainer"></div>');
        $('#feedbackHost').append('<div id="refreshNotification">New comments available <a id="feedbackRefreshLink">Click to Refresh</a></div>');
        $('#feedbackScrollContainer').append('<div id="feedbackSavedIssues"></div>');
        $('#feedbackScrollContainer').append('<div id="showResolvedIssuesBtn">SHOW RESOLVED COMMENTS</div>');
        $('#feedbackScrollContainer').append('<div id="feedbackResolvedIssuesDiv"></div>');
        $('#feedbackHeader').append('<div id="refreshFeedbackDiv"></div>');
        $('#feedbackHeader').append('<div id="feedbackToolbar" class="feedbackToolbar"></div>');
        $('#feedbackToolbar').append('<div id="feedbackNameHeader" class="pluginNameHeader">DISCUSS</div>');
        $('#feedbackToolbar').append('<div id="feedbackPageNameHeader" class="pageNameHeader"></div>');
        $('#feedbackToolbar').append('<div id="feedbackButtonHeader" class="pageButtonHeader"></div>');
        $('#feedbackButtonHeader').append('<a id="feedbackButtonBack" class="feedbackToolbarButton" title="Go to Previous Page" href="#"></a>');
        $('#feedbackButtonHeader').append('<a id="feedbackButtonForward" class="feedbackToolbarButton" title="Go to Next Page" href="#"></a>');
        $('#feedbackButtonHeader').append('<div id="feedbackButtonShowHidePins" class="feedbackToolbarButton pin_on" style="margin-right: 4px;"></div>');

        if(!isLocalChrome()) {
            $('#feedbackNewIssueContainer').append('<div id="feedbackNewIssuePin"/>');
            $('#feedbackNewIssueContainer').append('<div class="feedbackDivider"/>');
        } else {
            $('#feedbackButtonShowHidePins').removeClass('pin_on').addClass('pin_off');
        }

        $('#feedbackNewIssueContainer').append('<div id="feedbackNewIssue"/>');

        $('#feedbackResolvedIssuesDiv').append('<div id="hideResolvedIssuesBtn">HIDE RESOLVED COMMENTS</div><div class="feedbackDivider"></div>');

        // HTML for Loading placeholder 
        var placeholderHtml = function () {
            var results = [
                '<div class="feedbackIssue">',
                '   <div class="animated-background">',
                '       <div class="background-masker masker0"></div>',
                '       <div class="background-masker masker1"></div>',
                '       <div class="background-masker masker2"></div>',
                '       <div class="background-masker masker3"></div>',
                '       <div class="background-masker masker4"></div>',
                '       <div class="background-masker masker5"></div>',
                '       <div class="background-masker masker6"></div>',
                '       <div class="background-masker masker7"></div>',
                '       <div class="background-masker masker8"></div>',
                '       <div class="background-masker masker9"></div>',
                '       <div class="background-masker masker10"></div>',
                '       <div class="background-masker masker11"></div>',
                '   </div>',
                '</div>',
                '<div class="feedbackIssue">',
                '   <div class="animated-background">',
                '       <div class="background-masker masker0"></div>',
                '       <div class="background-masker masker1"></div>',
                '       <div class="background-masker masker2"></div>',
                '       <div class="background-masker masker3"></div>',
                '       <div class="background-masker masker4"></div>',
                '       <div class="background-masker masker5"></div>',
                '       <div class="background-masker masker6"></div>',
                '       <div class="background-masker masker7"></div>',
                '       <div class="background-masker masker8"></div>',
                '       <div class="background-masker masker9"></div>',
                '       <div class="background-masker masker10"></div>',
                '       <div class="background-masker masker11"></div>',
                '   </div>',
                '</div>',
                '<div class="feedbackIssue">',
                '   <div class="animated-background">',
                '       <div class="background-masker masker0"></div>',
                '       <div class="background-masker masker1"></div>',
                '       <div class="background-masker masker2"></div>',
                '       <div class="background-masker masker3"></div>',
                '       <div class="background-masker masker4"></div>',
                '       <div class="background-masker masker5"></div>',
                '       <div class="background-masker masker6"></div>',
                '       <div class="background-masker masker7"></div>',
                '       <div class="background-masker masker8"></div>',
                '       <div class="background-masker masker9"></div>',
                '       <div class="background-masker masker10"></div>',
                '       <div class="background-masker masker11"></div>',
                '   </div>',
                '</div>'
            ].join("");

            return results;
        };

        // HTML for note when there are no disucssions present for the project.
        var noDiscussionHtml = function () {
            var results = [
                '<div class="feedbackDivider"></div>',
                '<div class="noDiscussionText">',
                '   <p>No Discussions for this page.</p>',
                '   <span>',
                        (isLocalChrome() ? '' : 'Either select the button above to post to a location on the page, or use the field to post without location.'),
                '   </span>',
                '</div>'
            ].join("");

            return results;
        };

        $('#feedbackLoadingPlaceholder').append(placeholderHtml());
        $('#feedbackNoDiscussionsDiv').append(noDiscussionHtml());

        // Bind event function for updating the scroll container's height
        $(document).on('ContainerHeightChange', function () {
            updateContainerHeight();
        });

        // Bind event to toggle button for pins
        $('#feedbackButtonShowHidePins').click(function () {
            if (isLocalChrome()) return;

            if($(this).hasClass('pin_on')) {
                $(this).removeClass('pin_on');
                $(this).addClass('pin_off');
                $axpin.showPins(false);

                addRemoveFragmentVariable('dp', 0);
            } else {
                $(this).removeClass('pin_off');
                $(this).addClass('pin_on');
                $axpin.showPins(true);

                addRemoveFragmentVariable('dp');
            }
        });

        // Bind events to resolved issue buttons
        $('#showResolvedIssuesBtn').click(function() {
            //$('#feedbackSavedIssues').hide();
            $('#showResolvedIssuesBtn').hide();
            //$('#feedbackNewIssueContainer').hide();
            $('#feedbackResolvedIssuesDiv').show();

            var top = $('#feedbackSavedIssues').height() + 60;
            $('#feedbackScrollContainer').animate({ scrollTop: top }, 250, 'swing');

            $('#existingPinsOverlay').addClass('showResolvedPins');
        });
        $('#hideResolvedIssuesBtn').click(function() {
            //$('#feedbackSavedIssues').show();
            $('#showResolvedIssuesBtn').show();
            //$('#feedbackNewIssueContainer').show();
            $('#feedbackResolvedIssuesDiv').hide();

            $('#existingPinsOverlay').removeClass('showResolvedPins');
        });

        // Refresh issues notification
        $('#feedbackRefreshLink').click(function () {
            $('#feedbackLoadingPlaceholder').show();
            $('#showResolvedIssuesBtn').hide();

            if(window.$axpin != null && !isLocalChrome()) {
                $axpin.showPins(false, true);
                $axpin.prepareExistingPins();
            }

            clearAndLoadEntries(true);
            $('#refreshNotification').hide();
            $('#feedbackResolvedIssuesDiv').hide();

            return false;
        });

        // Load Extension if it exists for fileURLs only!
        $axscreenshot.loadExtensionIfFileUrl();

        // Automatic axure account auth check
        axureAuth();

        $(window).load(function () {
            $('.vsplitbar').mousemove(function () {
                // Resizes the refresh notification when the split bar is resized
                if($('#refreshNotification').is(':visible')) {
                    $('#refreshNotification').css('width', $('#feedbackHost').width());
                }
            });
        });
    };

    // Run when the document is ready
    $(document).ready(function () {
        //Following axure.utils function doesn't work properly in IE9
        $('<link>').appendTo('head').attr({
            rel: 'stylesheet',
            type: 'text/css',
            href: contentUrlPrefix + '/Content/plugins/feedback/styles/feedback8.css'
        });

        $('<link>').appendTo('head').attr({
            rel: 'stylesheet',
            type: 'text/css',
            href: contentUrlPrefix + '/Content/axSpeechBubble.css'
        });

        if (window.AXSHARE_ON_PREM) {
            $('<link>').appendTo('head').attr({
                rel: 'stylesheet',
                type: 'text/css',
                href: (window.location.protocol == 'http' ? 'http:' : 'https:') + '//www.axure.com/content/nexa_lightregular.css'
            });
        }

        $.getScript(contentUrlPrefix + "/Scripts/plugins/feedback/jquery.color.js");

        // Escape key event handler
        $(document).keyup(function (e) {
            if (e.keyCode === 27) {      // esc key
                // TODO: Maybe figure out a way to have esc key only perform one action given the situation

                // Hiding the new issue text area w/out pin
                hideNewIssueTextArea($('#feedbackNewIssue'));

                // Hide any anon name speech bubbles
                hideAnonNameSpeechBubble();

                // Hide any axure login speech bubbles
                hideAxureLoginSpeechBubble();
            }
        });

        // Mouse up events
        $(document).mouseup(function(e) {
            var $feedbackNewIssue = $('#feedbackNewIssue');
            var $activeAnonNameBubble = $('.anonNameChangeBubble:visible');
            var $activeAxureLoginBubble = $('.axureLoginBubble');

            // Hiding the new issue text area w/out pin
            if(!$feedbackNewIssue.is(e.target) && $feedbackNewIssue.has(e.target).length === 0) {
               hideNewIssueTextArea($feedbackNewIssue);
            }

            // Hide any active anon name change speech bubbles
            if (!$activeAnonNameBubble.is(e.target) && $activeAnonNameBubble.has(e.target).length === 0) {
                hideAnonNameSpeechBubble();
            }

            //
            if(!$activeAxureLoginBubble.is(e.target) && $activeAxureLoginBubble.has(e.target).length === 0) {
                hideAxureLoginSpeechBubble();
            }
        });

        //If the axDialog plugin isn't loaded yet, load it (used when loading within a prototype)
        if (!$.fn.axDialog) {
            $axure.utils.loadJS(contentUrlPrefix + '/Scripts/jquery.axDialog.js');
        }

        //If running within a prototype, check if feedback is enabled and load the plugin if so.
        if ($axure.player.prototype) {
            $axure.utils.loadJS(accountServiceUrl + '/Scripts/axshareUtils.js');
            $axure.utils.loadJS(accountServiceUrl + '/Scripts/sha512-min.js');
            $axure.utils.loadJS(accountServiceUrl + '/Scripts/axAccount.js');
            $axure.utils.loadJS(contentUrlPrefix + '/Scripts/plugins/feedback/axScreenshot.js');
            $axure.utils.loadJS(contentUrlPrefix + '/Scripts/plugins/feedback/axPin.js');

            //$axure.utils.loadJS(contentUrlPrefix + '/Scripts/jquery.inputHintOverlay.js');
            //$axure.utils.loadJS(contentUrlPrefix + '/Scripts/twitlimit-0.2.0.compressed.js');

            // Extra verification because IE 9/10 does not like loading js files in order (mainly for local case)
            if (!window.$axshare) window.$axshare = {};
            $axshare.useAxAccountDialog = false;

            (function finishInit() {
                if (!areToolsAllLoaded()) {
                    setTimeout(finishInit, 50);
                } else {
                    // Load tooling and apply functionality to Previous and Next Page buttons.
                    loadFeedbackTools();
                    fireWhenReady();

                    // Initial request to set up feedback
                    $.ajax({
                        type: 'GET',
                        url: feedbackServiceUrl + '/fbenabled',
                        data: {
                            shortcut: $axure.player.settings.projectId
                        },
                        success: function (response) {
                            //If the feedback is enabled for this prototype...
                            if (response.success && response.data.feedbackEnabled) {
                                readOnlyMode = response.readOnlyMode;
                                readOnlyMessage = response.readOnlyMessage;

                                // Store if prototype is part of subinstance
                                if(response.data.isSubInstance) { isSubInstance = response.data.isSubInstance; }

                                //If this is not an AxShare hosted prototype, authenticate the user and store the authCookie token, then load the plugin
                                //Else immediately load the plugin
                                if ($axure.player.settings.isAxshare === false) {
                                    if (response.data.passwordProtected) {
                                        getAuthTokenAndLoad();
                                    } else {
                                        $.ajax({
                                            type: 'GET',
                                            url: feedbackServiceUrl + '/Authenticate',
                                            data: {
                                                shortcut: $axure.player.settings.projectId,
                                                password: ''
                                            },
                                            success: function (response) {
                                                if (response.success) {
                                                    authCookieValue = response.authToken;
                                                    loadPlugin();
                                                    clearAndLoadEntries(false);
                                                }
                                            },
                                            dataType: 'jsonp'
                                        });
                                    }
                                } else {
                                    //For I.E., now we use authCookieValue all the time
                                    //since it doesn't allow 3rd party cookies by default
                                    //Checks for prototype token first, then user token
                                    var tokenString = getCookieValue(response.data.viewCookieName, true);
                                    if (tokenString == null) tokenString = getCookieValue(response.data.userCookieName, true);
                                    if (tokenString != null) authCookieValue = tokenString;

                                    loadPlugin();
                                    clearAndLoadEntries(false);
                                }

                                // Bug (resolved): Screwy IE8 Issues. Doesn't load up everything, so can't run. Should just restrict to not IE8
                                //var isIE8 = document.documentMode == 8 && window.msPerformance == null;
                                if (!$axscreenshot.isIE8) {
                                    // If fragment contains certain variables, redirect to feedback 
                                    (function waitforFeedbackButton() {
                                        if (window.loaded && $("#interfaceControlFrameHeader [pluginid='feedbackHost']").length) {
                                            showIssueOrComment();
                                        } else {
                                            setTimeout(waitforFeedbackButton, 1000);
                                        }
                                    })();
                                }
                            }
                        },
                        dataType: 'jsonp'
                    });
                }
            })();
        }
    });

    //---------------------------------------------------------------------------------------------------------------------------
    //----------END OF FEEDBACK PLUGIN LOADING-----------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------------------------


    //------------------------------------------------------------------------------------
    // The following 4 functions are used by the issue management page
    // 
    // This function creates an issue jQuery object from a given issue json object
    // It is used when an issue is displayed in a dialog on the issue management page
    //------------------------------------------------------------------------------------
    var managePageDeleteCallbackFunc = null;
    feedback.setIsManagePage = function(isManage) {
        isManagePage = isManage;
    }
    feedback.getWholeIssueQuery = function (issueJson, profile, deleteIssueCallbackFunc) {
        axureAuth();
        managePageDeleteCallbackFunc = deleteIssueCallbackFunc;
        feedback.setIsManagePage(true);

        var sortedIssue = fixIssueDatesAndSort([issueJson]);
        var issueQuery = getIssuesControl(sortedIssue, profile, true, sortedIssue[0].resolvedDate != null);

        // Screenshot Preloading Complete Callbacks
        issueQuery.find('.thumbnailImg').each(function () {
            $(this).load(function () {
                $axscreenshot.screenshotFinishLoadingCallback($(this).parent().parent());
                $axscreenshot.screenshotBindToDiv($(this).parent().parent());
            });
        });

        return issueQuery;
    };
    feedback.setReadOnlyModeAndMessage = function (roMode, roMessage) {
        readOnlyMode = roMode;
        readOnlyMessage = roMessage;
    };
})();